Skip to content

Commit c266fe1

Browse files
committed
Add: Add 2025/9/10
1 parent c659aea commit c266fe1

File tree

2 files changed

+311
-0
lines changed

2 files changed

+311
-0
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# 1733. Minimum Number of People to Teach
2+
3+
On a social network consisting of `m` users and some friendships between users, two users can communicate with each other if they know a common language.
4+
5+
You are given an integer `n`, an array `languages`, and an array `friendships` where:
6+
7+
- There are `n` languages numbered `1` through `n`,
8+
- `languages[i]` is the set of languages the $i^{th}$ user knows, and
9+
- `friendships[i] = [u_i, v_i]` denotes a friendship between the users `u_i` and `v_i`.
10+
11+
You can choose one language and teach it to some users so that all friends can communicate with each other.
12+
Return the minimum number of users you need to teach.
13+
14+
Note that friendships are not transitive, meaning if `x` is a friend of `y` and `y` is a friend of `z`, this doesn't guarantee that `x` is a friend of `z`.
15+
16+
**Constraints:**
17+
18+
- `2 <= n <= 500`
19+
- `languages.length == m`
20+
- `1 <= m <= 500`
21+
- `1 <= languages[i].length <= n`
22+
- `1 <= languages[i][j] <= n`
23+
- `1 <= u_i < v_i <= languages.length`
24+
- `1 <= friendships.length <= 500`
25+
- All tuples `(u_i, v_i)` are unique
26+
- `languages[i]` contains only unique values
27+
28+
## 基礎思路
29+
30+
本題的目標是挑選一種語言,教給最少數量的使用者,使所有「彼此為朋友」的使用者都能溝通。要注意溝通關係僅限於友誼邊本身、**不具傳遞性**。因此策略如下:
31+
32+
1. 只需關注那些「至少有一條友誼邊無共同語言」的使用者;其他人即使不學也不影響任何友誼邊的溝通。
33+
2. 在這些被標記的使用者中,統計每一種語言已被多少人掌握;若選擇教某語言,則只需把該語言教給「未掌握該語言的被標記使用者」。
34+
3. 為使教學人數最少,應選擇「在被標記人群中已知人數最多」的語言。答案即為:被標記人數 − 被標記人群中對此語言的已知人數最大值。
35+
36+
為了高效判定兩位使用者是否有共同語言,我們先建立一個可 **O(1)** 查詢的語言歸屬表,之後在掃描每條友誼邊時只需枚舉其中一位的語言清單並查表,比逐語言交集要快。
37+
38+
## 解題步驟
39+
40+
### Step 1:建立 O(1) 語言查表(初始化與填表)
41+
42+
先記下使用者數量,建立「平坦化」的語言成員矩陣,並依 `languages` 將使用者會的語言填入,之後可用常數時間查詢「某使用者是否會某語言」。
43+
44+
```typescript
45+
const userCount = languages.length;
46+
47+
// 平坦化矩陣用於 O(1) 查詢:membershipMatrix[userId * stride + languageId] = 1 代表使用者會此語言
48+
const languageStride = n + 1;
49+
const membershipMatrix = new Uint8Array((userCount + 1) * languageStride);
50+
51+
// 依使用者已知語言填滿成員矩陣
52+
for (let userId = 1; userId <= userCount; userId++) {
53+
const languageList = languages[userId - 1];
54+
for (
55+
let languageIndex = 0, languageListLength = languageList.length;
56+
languageIndex < languageListLength;
57+
languageIndex++
58+
) {
59+
const languageId = languageList[languageIndex];
60+
membershipMatrix[userId * languageStride + languageId] = 1;
61+
}
62+
}
63+
```
64+
65+
### Step 2:掃描每條友誼邊,標記「需要教學」的使用者
66+
67+
逐條友誼邊判斷兩人是否已有共同語言。為加速,枚舉語言較少的一方,對另一方用查表判定。若沒有共同語言,將兩位使用者標記為可能需要教學,並統計被標記總數。
68+
69+
```typescript
70+
// 追蹤存在至少一條無共同語言友誼的使用者
71+
const needsTeaching = new Uint8Array(userCount + 1);
72+
let needsTeachingCount = 0;
73+
74+
const friendshipCount = friendships.length;
75+
for (let friendshipIndex = 0; friendshipIndex < friendshipCount; friendshipIndex++) {
76+
const friendshipPair = friendships[friendshipIndex];
77+
const userA = friendshipPair[0];
78+
const userB = friendshipPair[1];
79+
80+
const languagesOfUserA = languages[userA - 1];
81+
const languagesOfUserB = languages[userB - 1];
82+
83+
let canCommunicate = false;
84+
85+
// 檢查兩人是否存在至少一種共同語言
86+
if (languagesOfUserA.length <= languagesOfUserB.length) {
87+
const baseOffset = userB * languageStride;
88+
for (
89+
let languageIndex = 0, languageListLength = languagesOfUserA.length;
90+
languageIndex < languageListLength;
91+
languageIndex++
92+
) {
93+
const languageId = languagesOfUserA[languageIndex];
94+
if (membershipMatrix[baseOffset + languageId] !== 0) {
95+
canCommunicate = true;
96+
break;
97+
}
98+
}
99+
} else {
100+
const baseOffset = userA * languageStride;
101+
for (
102+
let languageIndex = 0, languageListLength = languagesOfUserB.length;
103+
languageIndex < languageListLength;
104+
languageIndex++
105+
) {
106+
const languageId = languagesOfUserB[languageIndex];
107+
if (membershipMatrix[baseOffset + languageId] !== 0) {
108+
canCommunicate = true;
109+
break;
110+
}
111+
}
112+
}
113+
114+
// 若兩人無法溝通,將兩位使用者標記為可能需要教學
115+
if (!canCommunicate) {
116+
if (needsTeaching[userA] === 0) {
117+
needsTeaching[userA] = 1;
118+
needsTeachingCount++;
119+
}
120+
if (needsTeaching[userB] === 0) {
121+
needsTeaching[userB] = 1;
122+
needsTeachingCount++;
123+
}
124+
}
125+
}
126+
```
127+
128+
### Step 3:早停判斷
129+
130+
若沒有任何被標記者,代表所有友誼邊皆已可溝通,直接回傳 0。
131+
132+
```typescript
133+
// 若所有朋友組合皆可溝通,直接回傳 0
134+
if (needsTeachingCount === 0) {
135+
return 0;
136+
}
137+
```
138+
139+
### Step 4:統計被標記者中各語言的覆蓋數
140+
141+
只在被標記者集合中,計算每種語言已被幾人掌握,為後續挑選最佳語言做準備。
142+
143+
```typescript
144+
// 統計在「需要教學」的人群中,各語言已被多少人掌握
145+
const alreadyKnowCounts = new Uint16Array(n + 1);
146+
for (let userId = 1; userId <= userCount; userId++) {
147+
if (needsTeaching[userId] === 0) {
148+
continue;
149+
}
150+
const languageList = languages[userId - 1];
151+
for (
152+
let languageIndex = 0, languageListLength = languageList.length;
153+
languageIndex < languageListLength;
154+
languageIndex++
155+
) {
156+
const languageId = languageList[languageIndex];
157+
alreadyKnowCounts[languageId]++;
158+
}
159+
}
160+
```
161+
162+
### Step 5:找出在被標記者中最常見的語言
163+
164+
線性掃描語言 1…n,取得最大覆蓋數。
165+
166+
```typescript
167+
// 在被標記者中,找出已被最多人掌握的語言
168+
let maximumAlreadyKnow = 0;
169+
for (let languageId = 1; languageId <= n; languageId++) {
170+
const countForLanguage = alreadyKnowCounts[languageId];
171+
if (countForLanguage > maximumAlreadyKnow) {
172+
maximumAlreadyKnow = countForLanguage;
173+
}
174+
}
175+
```
176+
177+
### Step 6:計算最少教學人數並回傳
178+
179+
若選擇該最常見語言,只需教給「被標記總數 − 已會此語言的人數」即可。
180+
181+
```typescript
182+
// 教此最常見語言給其餘被標記者
183+
return needsTeachingCount - maximumAlreadyKnow;
184+
```
185+
186+
## 時間複雜度
187+
188+
- 建表:遍歷所有使用者的語言清單,為 $\sum_i |languages[i]|$。
189+
- 檢查友誼邊:對每條邊只枚舉語言較少的一方,成本為 $\sum_{(u,v)} \min(|L_u|,|L_v|)$,查表為 $O(1)$。
190+
- 統計與取最大:僅對被標記者再掃其語言清單($\le \sum_i |languages[i]|$)與一次 $O(n)$ 的取最大。
191+
- 總時間複雜度為 $O!\left(\sum_i |languages[i]| + \sum_{(u,v)} \min(|L_u|,|L_v|) + n\right)$。
192+
193+
> $O!\left(\sum_i |languages[i]| + \sum_{(u,v)} \min(|L_u|,|L_v|) + n\right)$
194+
195+
## 空間複雜度
196+
197+
- 平坦化成員矩陣需要 $O(m \times n)$ 空間,另有 `needsTeaching` 為 $O(m)$、`alreadyKnowCounts` 為 $O(n)$,整體受 $O(m \times n)$ 主導。
198+
- 總空間複雜度為 $O(m \times n)$。
199+
200+
> $O(m \times n)$
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
function minimumTeachings(n: number, languages: number[][], friendships: number[][]): number {
2+
const userCount = languages.length;
3+
4+
// Flattened matrix for O(1) lookup: membershipMatrix[userId * stride + languageId] = 1 if user knows language
5+
const languageStride = n + 1;
6+
const membershipMatrix = new Uint8Array((userCount + 1) * languageStride);
7+
8+
// Fill membership matrix based on users' known languages
9+
for (let userId = 1; userId <= userCount; userId++) {
10+
const languageList = languages[userId - 1];
11+
for (
12+
let languageIndex = 0, languageListLength = languageList.length;
13+
languageIndex < languageListLength;
14+
languageIndex++
15+
) {
16+
const languageId = languageList[languageIndex];
17+
membershipMatrix[userId * languageStride + languageId] = 1;
18+
}
19+
}
20+
21+
// Track users involved in friendships with no shared language
22+
const needsTeaching = new Uint8Array(userCount + 1);
23+
let needsTeachingCount = 0;
24+
25+
const friendshipCount = friendships.length;
26+
for (let friendshipIndex = 0; friendshipIndex < friendshipCount; friendshipIndex++) {
27+
const friendshipPair = friendships[friendshipIndex];
28+
const userA = friendshipPair[0];
29+
const userB = friendshipPair[1];
30+
31+
const languagesOfUserA = languages[userA - 1];
32+
const languagesOfUserB = languages[userB - 1];
33+
34+
let canCommunicate = false;
35+
36+
// Check for any common language between the two users
37+
if (languagesOfUserA.length <= languagesOfUserB.length) {
38+
const baseOffset = userB * languageStride;
39+
for (
40+
let languageIndex = 0, languageListLength = languagesOfUserA.length;
41+
languageIndex < languageListLength;
42+
languageIndex++
43+
) {
44+
const languageId = languagesOfUserA[languageIndex];
45+
if (membershipMatrix[baseOffset + languageId] !== 0) {
46+
canCommunicate = true;
47+
break;
48+
}
49+
}
50+
} else {
51+
const baseOffset = userA * languageStride;
52+
for (
53+
let languageIndex = 0, languageListLength = languagesOfUserB.length;
54+
languageIndex < languageListLength;
55+
languageIndex++
56+
) {
57+
const languageId = languagesOfUserB[languageIndex];
58+
if (membershipMatrix[baseOffset + languageId] !== 0) {
59+
canCommunicate = true;
60+
break;
61+
}
62+
}
63+
}
64+
65+
// If they cannot communicate, mark both users for possible teaching
66+
if (!canCommunicate) {
67+
if (needsTeaching[userA] === 0) {
68+
needsTeaching[userA] = 1;
69+
needsTeachingCount++;
70+
}
71+
if (needsTeaching[userB] === 0) {
72+
needsTeaching[userB] = 1;
73+
needsTeachingCount++;
74+
}
75+
}
76+
}
77+
78+
// Early return if all users already communicate with friends
79+
if (needsTeachingCount === 0) {
80+
return 0;
81+
}
82+
83+
// Count how many of the "needs teaching" users already know each language
84+
const alreadyKnowCounts = new Uint16Array(n + 1);
85+
for (let userId = 1; userId <= userCount; userId++) {
86+
if (needsTeaching[userId] === 0) {
87+
continue;
88+
}
89+
const languageList = languages[userId - 1];
90+
for (
91+
let languageIndex = 0, languageListLength = languageList.length;
92+
languageIndex < languageListLength;
93+
languageIndex++
94+
) {
95+
const languageId = languageList[languageIndex];
96+
alreadyKnowCounts[languageId]++;
97+
}
98+
}
99+
100+
// Find the language already known by the most "needs teaching" users
101+
let maximumAlreadyKnow = 0;
102+
for (let languageId = 1; languageId <= n; languageId++) {
103+
const countForLanguage = alreadyKnowCounts[languageId];
104+
if (countForLanguage > maximumAlreadyKnow) {
105+
maximumAlreadyKnow = countForLanguage;
106+
}
107+
}
108+
109+
// Teach this most-common language to the rest of the flagged users
110+
return needsTeachingCount - maximumAlreadyKnow;
111+
}

0 commit comments

Comments
 (0)