Skip to content

Commit aa3f365

Browse files
committed
Add: Add 2025/1/30
1 parent 20022d4 commit aa3f365

File tree

2 files changed

+357
-0
lines changed

2 files changed

+357
-0
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# 2493. Divide Nodes Into the Maximum Number of Groups
2+
3+
You are given a positive integer `n` representing the number of nodes in an undirected graph.
4+
The nodes are labeled from `1` to `n`.
5+
6+
You are also given a 2D integer array edges, where `edges[i] = [a_i, b_i]` indicates
7+
that there is a bidirectional edge between nodes `a_i` and `b_i`.
8+
Notice that the given graph may be disconnected.
9+
10+
Divide the nodes of the graph into `m` groups (1-indexed) such that:
11+
12+
- Each node in the graph belongs to exactly one group.
13+
- For every pair of nodes in the graph that are connected by an edge `[ai, bi]`,
14+
- if `a_i` belongs to the group with index `x`, and `b_i` belongs to the group with index `y`, then `|y - x| = 1`.
15+
16+
Return the maximum number of groups (i.e., maximum `m`) into which you can divide the nodes.
17+
Return `-1` if it is impossible to group the nodes with the given conditions.
18+
19+
## 基礎思路
20+
21+
這個問題可以被轉換成以下三個子問題:
22+
23+
1. 有效性檢查:確保節點之間的分層結構**
24+
- 核心問題是檢查節點集合是否能形成合法的層次結構。也就是每對相鄰節點之間的層級差必須剛好相差 1。
25+
- 如果無法滿足此條件,則無法進行有效分組。
26+
27+
2. 雙分圖判定
28+
- 每個連通子圖需要檢查是否是**雙分圖** (可以將節點分成兩個不相交的集合,並且每條邊都連接不同集合中的節點)。
29+
- 雙分圖的性質自然保證了相鄰節點位於不同層級上,從而滿足「層級差 1」的需求。反之,則無法有效地分層並劃分組,直接返回無效。
30+
31+
3. 最大組數計算:依據層級深度劃分
32+
- 當確認連通子圖是雙分圖後,我們可以根據每個節點的 **層級深度** 來進行分組。
33+
- 每個不同層級對應一個獨立的組,因此 **最大組數 = 子圖中節點的最大層次深度 + 1**
34+
- 節點越多層級,代表我們可以分配的組數越多。
35+
36+
37+
> Tips:
38+
>在進行雙分圖檢查的同時,可以同步記錄每個節點的層級深度,從而減少額外的遍歷操作,提高效率。
39+
40+
## 解題步驟
41+
42+
### Step 1: 建立 Adjacency List
43+
44+
首先,我們需要將給定的邊列表轉換為鄰接表形式,且轉成 0-based index。
45+
46+
```typescript
47+
// 無向圖的鄰接表
48+
const adjacencyList: number[][] = Array.from({ length: n }, () => []);
49+
50+
for (const [u, v] of edges) {
51+
// 轉換為 0-based index
52+
const uIndex = u - 1;
53+
const vIndex = v - 1;
54+
adjacencyList[uIndex].push(vIndex);
55+
adjacencyList[vIndex].push(uIndex);
56+
}
57+
```
58+
59+
### Step 2: 定義紀錄訪問狀態的數組
60+
61+
我們需要定義一個數組 `globalVisited` 來記錄節點的訪問狀態,並初始化為 `false`,表示未訪問。
62+
63+
```typescript
64+
const globalVisited: boolean[] = Array(n).fill(false);
65+
```
66+
67+
### Step 3: 檢查並計算最大層級 (getMaxLayerCount)
68+
69+
在此步驟中,我們需要對某個節點進行廣度優先搜索(BFS),以:
70+
1. 檢查相鄰節點之間的距離是否恰好差 1。
71+
2. 找出整個可達範圍中,節點所能達到的 **最大層級**(即最大 BFS 深度)。
72+
73+
> **作法**
74+
> - 建立一個 `distance` 陣列來儲存節點的 BFS 深度(預設值為 `-1`)。
75+
> - 將起點 `startNode``distance` 設為 0,並使用佇列(queue)進行層級遍歷。
76+
> - 過程中,若發現任何相鄰節點的距離差異不是 1,表示無法滿足層次分組需求,則回傳 `-1`
77+
> - 若全程合法,則回傳最大層級數(`maxLayer`)。
78+
79+
```typescript
80+
function getMaxLayerCount(startNode: number): number {
81+
const distance: number[] = Array(n).fill(-1);
82+
distance[startNode] = 0;
83+
84+
const queue: number[] = [startNode];
85+
let maxLayer = 1;
86+
87+
while (queue.length > 0) {
88+
const current = queue.shift()!;
89+
const currentDist = distance[current];
90+
91+
for (const neighbor of adjacencyList[current]) {
92+
if (distance[neighbor] === -1) {
93+
distance[neighbor] = currentDist + 1;
94+
maxLayer = Math.max(maxLayer, distance[neighbor] + 1);
95+
queue.push(neighbor);
96+
continue;
97+
}
98+
if (Math.abs(distance[neighbor] - currentDist) !== 1) {
99+
return -1; // 層級差異不為 1,無法分組
100+
}
101+
}
102+
}
103+
104+
return maxLayer; // 回傳最大層級數
105+
}
106+
```
107+
108+
### Step 4: 探索連通子圖 (exploreComponent)
109+
110+
為了找出整個圖中所有節點的分組方式,需要先找出每個 **連通子圖**,並對子圖進行雙分圖檢查及最大層級計算:
111+
112+
1. **收集子圖節點**
113+
-`startNode` 為起點,使用 BFS 走訪整個子圖;並透過 `globalVisited` 標記已探索的節點,防止重複處理。
114+
- 過程中順便檢查 **雙分圖衝突**(若有兩個相鄰節點的「BFS 距離」相同,表示衝突)。
115+
116+
2. **計算該子圖的最大層級**
117+
- 若子圖沒有衝突,則對該子圖中的每個節點呼叫 `getMaxLayerCount(node)`,找出最大層級值。
118+
- 若任何節點無法形成有效的層級分組,則整個子圖無效,回傳 `-1`
119+
120+
```typescript
121+
function exploreComponent(startNode: number): number {
122+
// 1. BFS 探索子圖 + 雙分圖檢查
123+
const queue: number[] = [startNode];
124+
const distance: number[] = Array(n).fill(-1);
125+
distance[startNode] = 0;
126+
globalVisited[startNode] = true;
127+
128+
const componentNodes: number[] = [startNode];
129+
130+
while (queue.length > 0) {
131+
const current = queue.shift()!;
132+
const currentDist = distance[current];
133+
134+
for (const neighbor of adjacencyList[current]) {
135+
if (distance[neighbor] === -1) {
136+
distance[neighbor] = currentDist + 1;
137+
queue.push(neighbor);
138+
componentNodes.push(neighbor);
139+
globalVisited[neighbor] = true;
140+
continue;
141+
}
142+
// 相鄰節點若是同一層 (距離相同),表示非雙分圖,無法分組
143+
if (distance[neighbor] === currentDist) {
144+
return -1;
145+
}
146+
}
147+
}
148+
149+
// 2. 尋找該子圖的最大層級
150+
let maxGroups = 1;
151+
for (const node of componentNodes) {
152+
const layerCount = getMaxLayerCount(node);
153+
if (layerCount === -1) {
154+
return -1;
155+
}
156+
maxGroups = Math.max(maxGroups, layerCount);
157+
}
158+
159+
return maxGroups;
160+
}
161+
```
162+
163+
### Step 5: 主流程 - 合計所有子圖的組數
164+
165+
最後,透過一個主迴圈將所有節點逐一檢查,對每個 **尚未造訪 (globalVisited 為 false)** 的節點呼叫 `exploreComponent(i)`
166+
-**子圖非法**(回傳 `-1`),整個問題就無解,回傳 `-1`
167+
- 否則將所有子圖的最大組數加總後,作為最終答案。
168+
169+
```typescript
170+
let totalMaxGroups = 0;
171+
172+
for (let i = 0; i < n; i++) {
173+
if (globalVisited[i]) {
174+
continue;
175+
}
176+
177+
const resultForComponent = exploreComponent(i);
178+
if (resultForComponent === -1) {
179+
return -1; // 任一子圖無效,直接結束
180+
}
181+
182+
totalMaxGroups += resultForComponent;
183+
}
184+
185+
return totalMaxGroups; // 回傳所有子圖組數的總和
186+
```
187+
188+
## 時間複雜度
189+
- 建立鄰接表需要將所有邊掃描一次,耗時 $O(E)$。
190+
- 處理所有節點與子圖時:
191+
1. `exploreComponent` 會以 BFS 走訪子圖中的每個節點,各子圖加總後約為 $O(N + E)$。
192+
2. 不過在 `exploreComponent` 中,還會對該子圖的每個節點呼叫 `getMaxLayerCount`(又是一個 BFS)。在最壞情況(整張圖是單一連通子圖)下,對 $N$ 個節點各做一次 BFS,單次 BFS 為 $O(N + E)$
193+
3. 因此最壞情況的整體時間複雜度可達 $O\bigl(N \times (N + E)\bigr)$
194+
195+
> $O\bigl(N \times (N + E)\bigr)$
196+
197+
## 空間複雜度
198+
- **鄰接表**:需要儲存所有節點與邊的關係,約為 $O(N + E)$。
199+
- **輔助陣列**:包含 `globalVisited``distance` 等大小為 $N$ 的結構,因此額外空間複雜度為 $O(N)$。
200+
- **整體**:主要被鄰接表佔用,故空間複雜度為
201+
202+
> $O(N + E)$
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
function magnificentSets(n: number, edges: number[][]): number {
2+
/**
3+
* 1. Build an adjacency list for the undirected graph.
4+
* We'll use 0-based indexing internally.
5+
*/
6+
// Adjacency list for the undirected graph
7+
const adjacencyList: number[][] = Array.from({ length: n }, () => []);
8+
9+
// Construct the adjacency list from the edges and convert to 0-based
10+
for (const [u, v] of edges) {
11+
// Convert 1-based input to 0-based
12+
const uIndex = u - 1;
13+
const vIndex = v - 1;
14+
adjacencyList[uIndex].push(vIndex);
15+
adjacencyList[vIndex].push(uIndex);
16+
}
17+
18+
/**
19+
* 2. We'll keep track of which nodes have been visited
20+
* in a global sense, to identify connected components.
21+
*/
22+
const globalVisited: boolean[] = Array(n).fill(false);
23+
24+
/**
25+
* 3. A BFS-based helper function that, given a node,
26+
* determines the maximum valid layering (or levels)
27+
* starting from that node, if valid.
28+
*
29+
* - We use 'distance[node]' to store the BFS depth.
30+
* - If we ever find an edge that connects nodes whose
31+
* depths differ by something other than 1, we return -1.
32+
* - Otherwise, the largest distance + 1 is the layer count.
33+
*
34+
* @param startNode The node to start the BFS from
35+
* @returns The maximum number of groups for this node, or -1 if invalid
36+
*/
37+
function getMaxLayerCount(startNode: number): number {
38+
const distance: number[] = Array(n).fill(-1);
39+
distance[startNode] = 0;
40+
41+
const queue: number[] = [startNode];
42+
43+
// At least one layer (the start node itself)
44+
let maxLayer = 1;
45+
46+
// Iterate over the queue to explore the nodes
47+
while (queue.length > 0) {
48+
const current = queue.shift()!;
49+
const currentDist = distance[current];
50+
51+
// Explore all neighbors of the current node
52+
for (const neighbor of adjacencyList[current]) {
53+
// If this neighbor hasn't been visited in this BFS
54+
if (distance[neighbor] === -1) {
55+
distance[neighbor] = currentDist + 1;
56+
maxLayer = Math.max(maxLayer, distance[neighbor] + 1);
57+
queue.push(neighbor);
58+
continue;
59+
}
60+
61+
// If the neighbor is visited, check the distance difference
62+
// For the grouping condition, |dist[u] - dist[v]| must be exactly 1
63+
if (Math.abs(distance[neighbor] - currentDist) !== 1) {
64+
return -1; // Invalid layering for this root
65+
}
66+
}
67+
}
68+
69+
return maxLayer;
70+
}
71+
72+
/**
73+
* 4. A function to explore (via BFS) all nodes in a single
74+
* connected component starting from 'startNode'.
75+
*
76+
* While exploring, it also checks for bipartite conflicts:
77+
* - We use 'dist[node]' as a color or BFS-layer marker.
78+
* - If two adjacent nodes have the same dist[], there's a conflict.
79+
* - If a conflict is found, return -1 immediately.
80+
*
81+
* Once the component is gathered, we try BFS from each
82+
* node in the component to find the best (max) layering.
83+
*
84+
* @param startNode The node to start the component exploration from
85+
* @returns The maximum number of groups for this component, or -1 if invalid
86+
*/
87+
function exploreComponent(startNode: number): number {
88+
// BFS to gather all nodes in the component and check bipartite constraints
89+
const queue: number[] = [startNode];
90+
const distance: number[] = Array(n).fill(-1);
91+
distance[startNode] = 0;
92+
globalVisited[startNode] = true;
93+
94+
const componentNodes: number[] = [startNode];
95+
96+
while (queue.length > 0) {
97+
const current = queue.shift()!;
98+
const currentDist = distance[current];
99+
100+
for (const neighbor of adjacencyList[current]) {
101+
if (distance[neighbor] === -1) {
102+
// Not yet visited in this component BFS
103+
distance[neighbor] = currentDist + 1;
104+
queue.push(neighbor);
105+
componentNodes.push(neighbor);
106+
globalVisited[neighbor] = true;
107+
continue;
108+
}
109+
110+
// If the neighbor has the same BFS distance => bipartite conflict
111+
// (same level => they'd be the same color)
112+
if (distance[neighbor] === currentDist) {
113+
return -1; // Not bipartite => fail
114+
}
115+
116+
}
117+
}
118+
119+
// Now, for the nodes in this component, find the maximum valid layering.
120+
let maxGroups = 1;
121+
for (const node of componentNodes) {
122+
const layerCount = getMaxLayerCount(node);
123+
if (layerCount === -1) {
124+
return -1; // The layering from 'node' wasn't valid
125+
}
126+
maxGroups = Math.max(maxGroups, layerCount);
127+
}
128+
129+
return maxGroups;
130+
}
131+
132+
/**
133+
* 5. Main loop over all nodes to process each connected component exactly once
134+
*/
135+
let totalMaxGroups = 0;
136+
137+
for (let i = 0; i < n; i++) {
138+
// Skip nodes that have been visited in previous components
139+
if (globalVisited[i]) {
140+
continue;
141+
}
142+
143+
const resultForComponent = exploreComponent(i);
144+
145+
// If the component was invalid, the entire graph is invalid
146+
if (resultForComponent === -1) {
147+
return -1;
148+
}
149+
150+
// Otherwise, add the result to the total
151+
totalMaxGroups += resultForComponent;
152+
}
153+
154+
return totalMaxGroups;
155+
}

0 commit comments

Comments
 (0)