Skip to content

Commit 20022d4

Browse files
committed
Add: Add 2025/1/29
1 parent 39ba58a commit 20022d4

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

684-Redundant Connection/Note.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# 684. Redundant Connection
2+
3+
In this problem, a tree is an undirected graph that is connected and has no cycles.
4+
5+
You are given a graph that started as a tree with `n` nodes labeled from `1` to `n`, with one additional edge added.
6+
The added edge has two different vertices chosen from `1` to `n`, and was not an edge that already existed.
7+
The graph is represented as an array edges of length `n` where `edges[i] = [a_i, b_i]` indicates that
8+
there is an edge between nodes `a_i` and `b_i` in the graph.
9+
10+
Return an edge that can be removed so that the resulting graph is a tree of `n` nodes.
11+
If there are multiple answers, return the answer that occurs last in the input.
12+
13+
## 基礎思路
14+
我們可以換個角度來看這個問題,我們可以將這個問題轉換成尋找一個圖中的cycle,並且返回這個cycle中的最後一個邊。
15+
16+
> Tips:
17+
> - 為了節省時間,我們可以使用 DSU (Disjoint Set Union) 來實現這個問題。
18+
> - 在圖中,如果兩個節點之間已經通過其他邊相連,它們會屬於同一個集合。
19+
> - 如果我們再加入一個邊,且這個邊的兩個節點屬於同一個集合,那麼這個邊就是多餘的。
20+
> - 為了節省空間,我們能用單個陣列追蹤每個節點的父節點,來代表集合。
21+
22+
## 解題步驟
23+
24+
### Step 1: 定義查找 root 的函數
25+
26+
```typescript
27+
const findRoot = (parent: number[], node: number): number => {
28+
if (parent[node] !== node) {
29+
// 路徑壓縮:將當前節點的父節點指向集合的根節點
30+
parent[node] = findRoot(parent, parent[node]);
31+
}
32+
return parent[node];
33+
};
34+
```
35+
36+
### Step 2: 定義合併集合的函數
37+
38+
```typescript
39+
const unionSets = (parent: number[], node1: number, node2: number): void => {
40+
// 將 node1 的根節點指向 node2 的根節點
41+
parent[findRoot(parent, node1)] = findRoot(parent, node2);
42+
};
43+
```
44+
45+
### Step 3: 初始化 parent 陣列
46+
47+
```typescript
48+
// 我們初始化一個長度為 edges.length + 1 的陣列,並且將每個節點的父節點設置為自己
49+
const parent = new Array(edges.length + 1).fill(0).map((_, index) => index);
50+
```
51+
52+
### Step 4: 遍歷邊,並且判斷是否有 cycle
53+
54+
```typescript
55+
for (const [node1, node2] of edges) {
56+
// 如果兩個節點有相同的根節點,則這個邊是多餘的
57+
if (findRoot(parent, node1) === findRoot(parent, node2)) {
58+
return [node1, node2];
59+
}
60+
// 否則,我們將這兩個節點合併到同一個集合中
61+
unionSets(parent, node1, node2);
62+
}
63+
```
64+
65+
## Step 5: 當沒有 cycle 時,返回空陣列
66+
67+
這是一個好習慣,確保我們的程式碼能夠處理所有的情況。即便題目中不會出現這種情況,但是我們還是應該要考慮到這種情況。
68+
69+
```typescript
70+
return [];
71+
```
72+
73+
## 時間複雜度
74+
75+
- 每次 findRoot 和 unionSets 操作的時間複雜度為 $O(\alpha(n))$,其中 $\alpha(n)$ 是[阿克曼函數](https://en.wikipedia.org/wiki/Ackermann_function)的反函數。由於 $\alpha(n)$ 的增長速度極慢,在實際應用中可視為常數時間。
76+
- 總體遍歷所有邊的操作 需要進行 $n$ 次 findRoot 和 unionSets 操作。時間複雜度為 $O(n \cdot \alpha(n))$。
77+
- 總體時間複雜度為 $O(n \cdot \alpha(n))$。
78+
79+
> $O(n \cdot \alpha(n))$
80+
81+
## 空間複雜度
82+
83+
- Parent 陣列的空間複雜度為 $O(n)$
84+
- 其他變數的空間複雜度為 $O(1)$
85+
- 總體空間複雜度為 $O(n)$
86+
87+
> $O(n)$

684-Redundant Connection/answer.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
function findRedundantConnection(edges: number[][]): number[] {
2+
/**
3+
* Helper function to find the representative (root) of a node in the union-finds structure.
4+
* Implements path compression to optimize future lookups.
5+
*/
6+
const findRoot = (parent: number[], node: number): number => {
7+
if (parent[node] !== node) {
8+
// Path compression: Assign the parent of the current node to the root of the set
9+
parent[node] = findRoot(parent, parent[node]);
10+
}
11+
return parent[node];
12+
};
13+
14+
/**
15+
* Helper function to merge two sets in the union-find structure.
16+
* It assigns the root of one node to the root of the other.
17+
*/
18+
const unionSets = (parent: number[], node1: number, node2: number): void => {
19+
parent[findRoot(parent, node1)] = findRoot(parent, node2);
20+
};
21+
22+
// Initialize the parent array where each node is its own parent initially
23+
const parent = new Array(edges.length + 1).fill(0).map((_, index) => index);
24+
25+
// Iterate through each edge to check if it forms a cycle
26+
for (const [node1, node2] of edges) {
27+
// If both nodes share the same root, this edge forms a cycle and is redundant
28+
if (findRoot(parent, node1) === findRoot(parent, node2)) {
29+
return [node1, node2];
30+
}
31+
// Otherwise, merge the two sets
32+
unionSets(parent, node1, node2);
33+
}
34+
35+
// No redundant edge found
36+
return [];
37+
}

0 commit comments

Comments
 (0)