|
| 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)$ |
0 commit comments