Skip to content

Commit 7d86b0b

Browse files
committed
Add: Add optimize solution
1 parent 0b07fa7 commit 7d86b0b

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# 2127. Maximum Employees to Be Invited to a Meeting
2+
3+
A company is organizing a meeting and has a list of `n` employees, waiting to be invited.
4+
They have arranged for a large circular table, capable of seating any number of employees.
5+
6+
The employees are numbered from `0` to `n - 1`.
7+
Each employee has a favorite person, and they will attend the meeting only if they can sit next to their favorite person at the table.
8+
The favorite person of an employee is not themselves.
9+
10+
Given a 0-indexed integer array `favorite`, where `favorite[i]` denotes the favorite person of the $i_{th}$ employee,
11+
return the maximum number of employees that can be invited to the meeting.
12+
13+
## 基礎思路
14+
15+
我們可以將問題轉化為一個「有向圖」(directed graph) 的問題:
16+
- 節點 (node) 代表員工。
17+
- 每個員工指向自己最喜歡的人 (favorite[i]),形成有向邊。
18+
19+
在一張圓桌上,如果一組員工能圍成一個「迴圈」(cycle),那麼就能彼此相鄰。
20+
所以有兩種情形可以幫我們找最大可邀請人數:
21+
1. **尋找圖中最長的迴圈 (cycle)**
22+
2. **尋找所有「互相喜歡」的 2-cycle (雙人互相指向) 加上各自延伸進來的「鏈」(chain) 長度後,再把它們合計起來。**
23+
24+
> Tips
25+
> - 若整個圖中最大的迴圈長度大於所有 2-cycle 加它們外圍鏈的總合,答案就是最大迴圈。
26+
> - 否則,我們就選擇所有 2-cycle 配上它們的鏈,並把總長加起來。
27+
> - 兩者取其最大值即為答案。
28+
29+
## 解題步驟
30+
31+
以下步驟對應到主要程式邏輯,並附上重點程式片段說明。
32+
33+
### Step 1: 預處理入度與初始化鏈長度
34+
35+
1. 計算每個節點的入度,記錄有多少人最喜歡該節點。
36+
2. 初始化所有節點的鏈長度為 `1`(因為自身就構成最短的鏈)。
37+
38+
```typescript
39+
// 紀錄員工人數
40+
const n = favorite.length;
41+
42+
// 紀錄每個人的入度 (被多少人喜歡)
43+
const inDegree = Array(n).fill(0);
44+
45+
// 計算每個節點的入度 (被多少人喜歡)
46+
for (const person of favorite) {
47+
inDegree[person] += 1;
48+
}
49+
50+
// 陣列紀錄節點是否被拜訪過,以及每個節點的最長鏈長度
51+
const visited = Array(n).fill(false);
52+
const longestChain = Array(n).fill(1);
53+
```
54+
55+
### Step 2: 拓撲排序處理鏈長度
56+
57+
1. 將入度為 `0` 的節點加入隊列,並標記為已拜訪。
58+
2. 不斷處理隊列中的節點,更新其「鏈長度」到下一個節點,並減少下一個節點的入度。
59+
3. 如果下一個節點的入度變為 `0`,加入隊列。
60+
61+
```typescript
62+
// 初始化隊列
63+
const queue: number[] = [];
64+
65+
// 將所有入度為 0 (沒有人喜歡) 的節點加入隊列,並標記為已拜訪
66+
for (let i = 0; i < n; i++) {
67+
if (inDegree[i] === 0) {
68+
queue.push(i);
69+
visited[i] = true;
70+
}
71+
}
72+
73+
// 進行拓撲排序,計算鏈長度
74+
while (queue.length > 0) {
75+
const currentNode = queue.pop()!;
76+
const nextNode = favorite[currentNode];
77+
78+
// 若把 currentNode 接在 nextNode 前面,可以使 longestChain[nextNode] 更長
79+
longestChain[nextNode] = Math.max(longestChain[nextNode], longestChain[currentNode] + 1);
80+
81+
// 因為使用了 currentNode 這條邊,所以 nextNode 的入度要減 1
82+
inDegree[nextNode]--;
83+
84+
// 如果 nextNode 的入度歸 0,則放入 queue,並標記為已拜訪
85+
if (inDegree[nextNode] === 0) {
86+
queue.push(nextNode);
87+
visited[nextNode] = true;
88+
}
89+
}
90+
```
91+
92+
### Step 3: 計算圖中的迴圈與 2-cycle
93+
94+
1. 遍歷所有節點,找出尚未處理過的迴圈。
95+
2. 對於長度大於 `2` 的迴圈,記錄最大迴圈長度。
96+
3. 對於 2-cycle,計算兩邊鏈的總長度並累加。
97+
98+
> Tips
99+
> 我們在該步驟中同時紀錄最大迴圈長度與所有 2-cycle 的鏈長度總和。
100+
> 這能提升效率。
101+
102+
```typescript
103+
// 最大迴圈長度
104+
let maxCycleLength = 0;
105+
106+
// 所有 2-cycle 的鏈長度總和
107+
let twoCycleChainLength = 0;
108+
109+
// 遍歷所有節點,找出迴圈與 2-cycle 的鏈長度
110+
for (let i = 0; i < n; i++) {
111+
// 若節點已經被拜訪過,則跳過
112+
if (visited[i]) continue;
113+
114+
let currentNode = i;
115+
let cycleLength = 0;
116+
117+
// 遍歷迴圈,並標記節點為已拜訪
118+
while (!visited[currentNode]) {
119+
visited[currentNode] = true;
120+
currentNode = favorite[currentNode];
121+
cycleLength++;
122+
}
123+
124+
// 若迴圈長度大於 2,則更新最大迴圈長度
125+
if (cycleLength > 2) {
126+
maxCycleLength = Math.max(maxCycleLength, cycleLength);
127+
}
128+
// 若剛好是 2-cycle,則計算兩邊鏈的總長度
129+
else if (cycleLength === 2) {
130+
const node1 = i;
131+
const node2 = favorite[i];
132+
twoCycleChainLength += longestChain[node1] + longestChain[node2];
133+
}
134+
}
135+
```
136+
137+
### Step 4: 返回最大值
138+
139+
將最大迴圈長度與所有 2-cycle 加上其鏈的總長度比較,取最大值作為答案。
140+
141+
```typescript
142+
return Math.max(maxCycleLength, twoCycleChainLength);
143+
```
144+
145+
## 時間複雜度
146+
- 計算入度與初始化鏈長度需要 $O(n)$。
147+
- 拓撲排序處理鏈長度需要 $O(n)$,因為每條邊只處理一次。
148+
- 找出所有迴圈與 2-cycle 需要 $O(n)$。
149+
150+
> $O(n)$
151+
152+
## 空間複雜度
153+
- `inDegree``visited``longestChain` 等陣列需要 $O(n)$。
154+
- 拓撲排序隊列的空間最多為 $O(n)$。
155+
156+
> $O(n)$
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Finds the maximum number of people that can be invited to sit together at a round table.
3+
*
4+
* The solution considers:
5+
* 1) The length of the largest cycle in the graph.
6+
* 2) The combined length of chains attached to all mutual-favorite pairs (2-cycles).
7+
*
8+
* @param favorite - An array where each index `i` represents a person, and `favorite[i]` is the person they favor.
9+
* @returns The maximum number of people that can be invited.
10+
*/
11+
function maximumInvitations(favorite: number[]): number {
12+
const n = favorite.length;
13+
14+
// Array to track the number of people favoring each person.
15+
const inDegree = Array(n).fill(0);
16+
17+
// Compute the in-degree for each person.
18+
for (const person of favorite) {
19+
inDegree[person] += 1;
20+
}
21+
22+
// Arrays for tracking visited nodes and chain depths.
23+
const visited = Array(n).fill(false); // Whether a node has been visited.
24+
const longestChain = Array(n).fill(1); // Longest chain depth ending at each node.
25+
26+
// Queue to process nodes with no incoming edges (in-degree = 0).
27+
const queue: number[] = [];
28+
for (let i = 0; i < n; i++) {
29+
if (inDegree[i] === 0) {
30+
queue.push(i);
31+
visited[i] = true; // Mark as visited during chain processing.
32+
}
33+
}
34+
35+
// Perform topological sorting to compute chain depths.
36+
while (queue.length > 0) {
37+
const currentNode = queue.pop()!;
38+
const nextNode = favorite[currentNode];
39+
40+
// Reduce the in-degree of the next node and update its chain depth.
41+
inDegree[nextNode]--;
42+
longestChain[nextNode] = Math.max(longestChain[nextNode], longestChain[currentNode] + 1);
43+
44+
// If the next node has no more incoming edges, add it to the queue.
45+
if (inDegree[nextNode] === 0) {
46+
queue.push(nextNode);
47+
visited[nextNode] = true;
48+
}
49+
}
50+
51+
let maxCycleLength = 0; // Largest cycle length in the graph.
52+
let twoCycleChainLength = 0; // Total chain length contributed by all 2-cycles.
53+
54+
// Iterate through all nodes to find cycles and calculate chain contributions.
55+
for (let i = 0; i < n; i++) {
56+
if (visited[i]) continue; // Skip nodes already processed.
57+
58+
let currentNode = i;
59+
let cycleLength = 0;
60+
61+
// Traverse the cycle and mark nodes as visited.
62+
while (!visited[currentNode]) {
63+
visited[currentNode] = true;
64+
currentNode = favorite[currentNode];
65+
cycleLength++;
66+
}
67+
68+
// If the cycle is larger than 2, update maxCycleLength.
69+
if (cycleLength > 2) {
70+
maxCycleLength = Math.max(maxCycleLength, cycleLength);
71+
}
72+
// If it's a 2-cycle, calculate the chain contributions.
73+
else if (cycleLength === 2) {
74+
const node1 = i;
75+
const node2 = favorite[i];
76+
twoCycleChainLength += longestChain[node1] + longestChain[node2];
77+
}
78+
}
79+
80+
// Return the maximum between the largest cycle and the combined 2-cycle chain length.
81+
return Math.max(maxCycleLength, twoCycleChainLength);
82+
}

0 commit comments

Comments
 (0)