Medium
题目描述
在一个由 m 个用户组成的社交网络中,有一些用户之间存在友谊关系。如果两个用户知道一种共同的语言,他们就可以相互沟通。
给你一个整数 n,一个数组 languages,和一个数组 friendships,其中:
- 有 n 种语言,编号为 1 到 n
- languages[i] 是第 i 个用户知道的语言集合
- friendships[i] = [ui, vi] 表示用户 ui 和 vi 之间存在友谊关系
你可以选择一种语言并教给一些用户,使得所有朋友都能相互沟通。返回你需要教授的最少用户数量。
注意:友谊关系不具有传递性,即如果 x 是 y 的朋友,y 是 z 的朋友,这并不保证 x 是 z 的朋友。
示例 1:
输入:n = 2, languages = [[1],[2],[1,2]], friendships = [[1,2],[1,3],[2,3]]
输出:1
解释:你可以教用户 1 第二种语言,或者教用户 2 第一种语言。
示例 2:
输入:n = 3, languages = [[2],[1,3],[1,2],[3]], friendships = [[1,4],[1,2],[3,4],[2,3]]
输出:2
解释:教第三种语言给用户 1 和 3,需要教授 2 个用户。
约束条件:
- 2 <= n <= 500
- languages.length == m
- 1 <= m <= 500
- 1 <= languages[i].length <= n
- 1 <= languages[i][j] <= n
- 1 <= ui < vi <= languages.length
- 1 <= friendships.length <= 500
- 所有元组 (ui, vi) 都是唯一的
- languages[i] 只包含唯一值
解题思路
这是一道贪心算法题目,核心思路是找到教授哪种语言能使需要教授的人数最少。
解题思路:
识别无法沟通的朋友对:首先遍历所有友谊关系,找出哪些朋友对之间没有共同语言,这些朋友对无法直接沟通。
暴力枚举每种语言:对于每种可能的语言(1到n),计算如果选择这种语言作为教授语言,需要教授多少人。
计算教授成本:对于每种语言,找出所有无法沟通的朋友对中,有多少人不会这种语言。这些人就是需要教授的对象。注意同一个人可能出现在多个朋友对中,但只需要教授一次。
选择最优方案:选择需要教授人数最少的语言。
算法步骤:
- 将每个用户的语言转换为集合,便于快速查找
- 找出所有无法沟通的朋友对
- 对每种语言,统计在无法沟通的朋友对中,不会该语言的用户数量
- 返回最小的教授人数
时间复杂度主要来自于枚举语言和统计用户,整体是可接受的。
代码实现
class Solution {
public:
int minimumTeachings(int n, vector<vector<int>>& languages, vector<vector<int>>& friendships) {
int m = languages.size();
// 将语言列表转换为集合,便于快速查找
vector<unordered_set<int>> langSet(m);
for (int i = 0; i < m; i++) {
for (int lang : languages[i]) {
langSet[i].insert(lang);
}
}
// 找出无法沟通的朋友对
vector<pair<int, int>> cannotCommunicate;
for (auto& friendship : friendships) {
int u = friendship[0] - 1, v = friendship[1] - 1; // 转换为0索引
// 检查是否有共同语言
bool hasCommonLang = false;
for (int lang : langSet[u]) {
if (langSet[v].count(lang)) {
hasCommonLang = true;
break;
}
}
if (!hasCommonLang) {
cannotCommunicate.push_back({u, v});
}
}
// 如果所有朋友都能沟通,则不需要教授任何人
if (cannotCommunicate.empty()) return 0;
int minTeach = m + 1;
// 尝试每种语言
for (int lang = 1; lang <= n; lang++) {
unordered_set<int> needTeach;
// 对于每个无法沟通的朋友对
for (auto& pair : cannotCommunicate) {
int u = pair.first, v = pair.second;
// 如果用户u不会这种语言,需要教授
if (langSet[u].find(lang) == langSet[u].end()) {
needTeach.insert(u);
}
// 如果用户v不会这种语言,需要教授
if (langSet[v].find(lang) == langSet[v].end()) {
needTeach.insert(v);
}
}
minTeach = min(minTeach, (int)needTeach.size());
}
return minTeach;
}
};
class Solution:
def minimumTeachings(self, n: int, languages: List[List[int]], friendships: List[List[int]]) -> int:
m = len(languages)
# 将语言列表转换为集合,便于快速查找
lang_sets = [set(langs) for langs in languages]
# 找出无法沟通的朋友对
cannot_communicate = []
for u, v in friendships:
u, v = u - 1, v - 1 # 转换为0索引
# 检查是否有共同语言
if not (lang_sets[u] & lang_sets[v]):
cannot_communicate.append((u, v))
# 如果所有朋友都能沟通,则不需要教授任何人
if not cannot_communicate:
return 0
min_teach = m + 1
# 尝试每种语言
for lang in range(1, n + 1):
need_teach = set()
# 对于每个无法沟通的朋友对
for u, v in cannot_communicate:
# 如果用户u不会这种语言,需要教授
if lang not in lang_sets[u]:
need_teach.add(u)
# 如果用户v不会这种语言,需要教授
if lang not in lang_sets[v]:
need_teach.add(v)
min_teach = min(min_teach, len(need_teach))
return min_teach
public class Solution {
public int MinimumTeachings(int n, int[][] languages, int[][] friendships) {
int m = languages.Length;
// 将语言列表转换为集合,便于快速查找
HashSet<int>[] langSets = new HashSet<int>[m];
for (int i = 0; i < m; i++) {
langSets[i] = new HashSet<int>(languages[i]);
}
// 找出无法沟通的朋友对
List<(int, int)> cannotCommunicate = new List<(int, int)>();
foreach (var friendship in friendships) {
int u = friendship[0] - 1, v = friendship[1] - 1; // 转换为0索引
// 检查是否有共同语言
bool hasCommonLang = false;
foreach (int lang in langSets[u]) {
if (langSets[v].Contains(lang)) {
hasCommonLang = true;
break;
}
}
if (!hasCommonLang) {
cannotCommunicate.Add((u, v));
}
}
// 如果所有朋友都能沟通,则不需要教授任何人
if (cannotCommunicate.Count == 0) return 0;
int minTeach = m + 1;
// 尝试每种语言
for (int lang = 1; lang <= n; lang++) {
HashSet<int> needTeach = new HashSet<int>();
// 对于每个无法沟通的朋友对
foreach (var (u, v) in cannotCommunicate) {
// 如果用户u不会这种语言,需要教授
if (!langSets[u].Contains(lang)) {
needTeach.Add(u);
}
// 如果用户v不会这种语言,需要教授
if (!langSets[v].Contains(lang)) {
needTeach.Add(v);
}
}
minTeach = Math.Min(minTeach, needTeach.Count);
}
return minTeach;
}
}
/**
* @param {number} n
* @param {number[][]} languages
* @param {number[][]} friendships
* @return {number}
*/
var minimumTeachings = function(n, languages, friendships) {
const langSets = languages.map(langs => new Set(langs));
// Find friendships that can't communicate
const problematicFriendships = [];
for (const [u, v] of friendships) {
const uLangs = langSets[u - 1];
const vLangs = langSets[v - 1];
let canCommunicate = false;
for (const lang of uLangs) {
if (vLangs.has(lang)) {
canCommunicate = true;
break;
}
}
if (!canCommunicate) {
problematicFriendships.push([u - 1, v - 1]);
}
}
if (problematicFriendships.length === 0) return 0;
// Get all users involved in problematic friendships
const problematicUsers = new Set();
for (const [u, v] of problematicFriendships) {
problematicUsers.add(u);
problematicUsers.add(v);
}
let minTeachings = problematicUsers.size;
// Try teaching each language
for (let lang = 1; lang <= n; lang++) {
const usersToTeach = new Set();
for (const [u, v] of problematicFriendships) {
const uKnowsLang = langSets[u].has(lang);
const vKnowsLang = langSets[v].has(lang);
if (!uKnowsLang) usersToTeach.add(u);
if (!vKnowsLang) usersToTeach.add(v);
}
minTeachings = Math.min(minTeachings, usersToTeach.size);
}
return minTeachings;
};
复杂度分析
| 复杂度类型 | 复杂度分析 |
|---|---|
| 时间复杂度 | O(F × L + n × F × m),其中 F 是友谊关系数量,L 是平均语言数量,m 是用户数量。第一部分是找无法沟通的朋友对,第二部分是枚举语言计算教授成本 |
| 空间复杂度 | O(m × L + F),用于存储语言集合和无法沟通的朋友对列表 |