Minimum Number of People to Teach
MedPrompt
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.
You are given an integer n, an array languages, and an array friendships where:
- There are
nlanguages numbered1throughn, languages[i]is the set of languages theith user knows, andfriendships[i] = [ui, vi]denotes a friendship between the usersui andvi.
You can choose one language and teach it to some users so that all friends can communicate with each other. Return the minimum number of users you need to teach.
Note that friendships are not transitive, meaning ifx is a friend of y and y is a friend of z, this doesn't guarantee that x is a friend of z.
Example 1:
Input: n = 2, languages = [[1],[2],[1,2]], friendships = [[1,2],[1,3],[2,3]]
Output: 1
Explanation: You can either teach user 1 the second language or user 2 the first language.Example 2:
Input: n = 3, languages = [[2],[1,3],[1,2],[3]], friendships = [[1,4],[1,2],[3,4],[2,3]]
Output: 2
Explanation: Teach the third language to users 1 and 3, yielding two users to teach.
Constraints:
2 <= n <= 500languages.length == m1 <= m <= 5001 <= languages[i].length <= n1 <= languages[i][j] <= n1 <= ui < vi <= languages.length1 <= friendships.length <= 500- All tuples
(ui, vi)are unique languages[i]contains only unique values
Approaches
2 approaches with complexity analysis and trade-offs.
This approach iterates through every possible language from 1 to n that we could teach. For each candidate language, it determines the number of users that would need to be taught this language to satisfy all friendship communication requirements. The minimum count across all languages is the answer.
Algorithm
- Initialize
minTeachingsto a very large value (e.g., the total number of users). - For each language
lfrom1ton:- Create an empty set
usersToTeachForLto store unique users who need to be taught languagel. - For each friendship
[u, v]:- Check if users
uandvshare a common language. This check itself can be done by iterating through the languages of one user and checking for existence in the other's language list. - If they do not share a common language:
- Check if user
ualready knows languagel. If not, adduto theusersToTeachForLset. - Check if user
valready knows languagel. If not, addvto theusersToTeachForLset.
- Check if user
- Check if users
- After checking all friendships, the size of the
usersToTeachForLset is the number of teachings required for languagel. - Update
minTeachings = min(minTeachings, usersToTeachForL.size()).
- Create an empty set
- Return
minTeachings.
Walkthrough
The algorithm iterates through each language l from 1 to n. For each l, we calculate the number of people to teach. We initialize a set, usersToTeach, to keep track of them to avoid duplicates. We then iterate through every friendship [u, v]. For each friendship, we first check if users u and v can already communicate. This is done by checking if their sets of known languages have a non-empty intersection. If they cannot communicate, they form a 'disconnected pair'. For this pair to communicate using language l, both must know it. We check if user u already knows language l. If not, we add u to our usersToTeach set for language l. We do the same for user v. After checking all friendships, the size of the usersToTeach set gives the total number of teachings required for language l. We keep track of the minimum size found so far across all languages. Finally, after checking all n languages, we return the overall minimum.
To make language lookups efficient, we first convert the input languages array into an array of HashSets.
class Solution { public int minimumTeachings(int n, int[][] languages, int[][] friendships) { int m = languages.length; // Convert languages to Sets for easier lookup Set<Integer>[] langSets = new HashSet[m + 1]; for (int i = 1; i <= m; i++) { langSets[i] = new HashSet<>(); for (int lang : languages[i - 1]) { langSets[i].add(lang); } } int minTeachings = m; // Maximum possible teachings is m for (int l = 1; l <= n; l++) { Set<Integer> usersToTeach = new HashSet<>(); for (int[] friendship : friendships) { int u = friendship[0]; int v = friendship[1]; if (!canCommunicate(langSets, u, v)) { if (!langSets[u].contains(l)) { usersToTeach.add(u); } if (!langSets[v].contains(l)) { usersToTeach.add(v); } } } minTeachings = Math.min(minTeachings, usersToTeach.size()); } return minTeachings; } private boolean canCommunicate(Set<Integer>[] langSets, int u, int v) { Set<Integer> langU = langSets[u]; Set<Integer> langV = langSets[v]; // Iterate over the smaller set for efficiency if (langU.size() > langV.size()) { Set<Integer> temp = langU; langU = langV; langV = temp; } for (int lang : langU) { if (langV.contains(lang)) { return true; } } return false; }}Complexity
Time
O(n * F * L), where `n` is the number of languages, `F` is the number of friendships, and `L` is the maximum number of languages a user knows. We iterate through `n` languages. For each, we iterate through `F` friendships. The `canCommunicate` check takes `O(L)` time with pre-processed sets.
Space
O(m * L + m), where `m` is the number of users and `L` is the maximum number of languages a user knows. `O(m*L)` space is used to store the language sets for each user, and the `usersToTeach` set can hold at most `m` users.
Trade-offs
Pros
The logic is straightforward and directly models the problem of trying each possible language.
Cons
This approach is inefficient because it repeatedly calculates which friendships are 'disconnected' for each of the
nlanguages. This leads to a higher time complexity.
This approach improves upon the brute-force method by first identifying the core problem: we only need to teach users who are part of at least one 'disconnected' friendship (where the two friends share no common language). By first finding this set of users, we can then find the best language to teach them, avoiding redundant checks.
Algorithm
- Pre-process the
languagesarray into a more efficient data structure, like an array ofHashSets, for quick language lookups for each user. - Create an empty set
disconnectedUsers. - Iterate through each friendship
[u, v]:- Check if
uandvshare a common language. - If they don't, add both
uandvtodisconnectedUsers.
- Check if
- If
disconnectedUsersis empty, it means all friends can communicate, so return 0. - Create a frequency array
langCountsof sizen+1, initialized to zeros. - For each
userindisconnectedUsers:- For each
languagethatuserknows:- Increment
langCounts[language].
- Increment
- For each
- Find the maximum value
maxFreqinlangCounts. - The result is
disconnectedUsers.size() - maxFreq.
Walkthrough
The key insight is that any user who can already communicate with all their friends doesn't need to be taught a new language. The problem is confined to users in friendships that lack a common language.
Step 1: Identify Disconnected Users. We iterate through all friendships once. For each friendship [u, v], we check if they can communicate. If not, we add both u and v to a set called disconnectedUsers. This set now contains all users who are part of at least one communication problem. If this set is empty after checking all friendships, it means everyone can communicate, and the answer is 0.
Step 2: Find the Most Common Language. The goal is to teach a single language l to the users in disconnectedUsers who don't already know it. The number of people to teach for a language l is |disconnectedUsers| - (number of users in disconnectedUsers who already know l). To minimize this value, we need to maximize the number of users in disconnectedUsers who already know a common language. So, we find which language is most prevalent among the disconnectedUsers. We can use a frequency map (or an array) to count how many users in disconnectedUsers know each language.
Step 3: Calculate the Result. We find the maximum frequency maxFreq from our frequency map. This maxFreq represents the largest group of users within disconnectedUsers that we don't have to teach, if we pick their common language. The minimum number of users to teach is therefore the total number of disconnected users minus this maximum frequency: disconnectedUsers.size() - maxFreq.
class Solution { public int minimumTeachings(int n, int[][] languages, int[][] friendships) { int m = languages.length; Set<Integer>[] langSets = new HashSet[m + 1]; for (int i = 1; i <= m; i++) { langSets[i] = new HashSet<>(); for (int lang : languages[i - 1]) { langSets[i].add(lang); } } Set<Integer> disconnectedUsers = new HashSet<>(); for (int[] friendship : friendships) { int u = friendship[0]; int v = friendship[1]; if (!canCommunicate(langSets, u, v)) { disconnectedUsers.add(u); disconnectedUsers.add(v); } } if (disconnectedUsers.isEmpty()) { return 0; } int[] langFrequency = new int[n + 1]; int maxFreq = 0; for (int user : disconnectedUsers) { for (int lang : langSets[user]) { langFrequency[lang]++; maxFreq = Math.max(maxFreq, langFrequency[lang]); } } return disconnectedUsers.size() - maxFreq; } private boolean canCommunicate(Set<Integer>[] langSets, int u, int v) { Set<Integer> langU = langSets[u]; Set<Integer> langV = langSets[v]; if (langU.size() > langV.size()) { Set<Integer> temp = langU; langU = langV; langV = temp; } for (int lang : langU) { if (langV.contains(lang)) { return true; } } return false; }}Complexity
Time
O(m*L + F*L), where `m` is users, `F` is friendships, and `L` is max languages/user. This consists of `O(m*L)` to build language sets, `O(F*L)` to find disconnected users, and `O(|disconnectedUsers| * L)` (at most `O(m*L)`) to count frequencies.
Space
O(m * L + n), where `m` is the number of users, `L` is the max languages per user, and `n` is the number of languages. `O(m*L)` for language sets, `O(m)` for the `disconnectedUsers` set, and `O(n)` for the frequency count array.
Trade-offs
Pros
Highly efficient as it avoids redundant computations by identifying the set of relevant users only once.
The time complexity is significantly better than the brute-force approach, making it suitable for larger constraints.
Cons
The logic is slightly more complex, involving multiple steps and data structures (sets, frequency arrays).
Solutions
Solution
class Solution {public int minimumTeachings(int n, int[][] languages, int[][] friendships) { Set<Integer> s = new HashSet<>(); for (var e : friendships) { int u = e[0], v = e[1]; if (!check(u, v, languages)) { s.add(u); s.add(v); } } if (s.isEmpty()) { return 0; } int[] cnt = new int[n + 1]; for (int u : s) { for (int l : languages[u - 1]) { ++cnt[l]; } } int mx = 0; for (int v : cnt) { mx = Math.max(mx, v); } return s.size() - mx; }private boolean check(int u, int v, int[][] languages) { for (int x : languages[u - 1]) { for (int y : languages[v - 1]) { if (x == y) { return true; } } } return false; }}class Solution {public: int minimumTeachings(int n, vector<vector<int>> &languages, vector<vector<int>> &friendships) { unordered_set<int> s; for (auto &e : friendships) { int u = e[0], v = e[1]; if (!check(u, v, languages)) { s.insert(u); s.insert(v); } } if (s.empty()) { return 0; } vector<int> cnt(n + 1); for (int u : s) { for (int &l : languages[u - 1]) { ++cnt[l]; } } return s.size() - *max_element(cnt.begin(), cnt.end()); } bool check(int u, int v, vector<vector<int>> &languages) { for (int x : languages[u - 1]) { for (int y : languages[v - 1]) { if (x == y) { return true; } } } return false; }};class Solution: def minimumTeachings(self, n: int, languages: List[List[int]], friendships: List[List[int]]) -> int: def check(u, v): for x in languages[u - 1]: for y in languages[v - 1]: if x == y: return True return False s = set() for u, v in friendships: if not check(u, v): s . add(u) s . add(v) cnt = Counter() for u in s: for l in languages[u - 1]: cnt[l] += 1 return len(s) - max(cnt . values(), default=0)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.