Taro Logo

Minimum Number of People to Teach

Medium
Asked by:
Profile picture
34 views
Topics:
ArraysGreedy Algorithms

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 n languages numbered 1 through n,
  • languages[i] is the set of languages the i​​​​​​th​​​​ user knows, and
  • friendships[i] = [u​​​​​​i​​​, v​​​​​​i] denotes a friendship between the users u​​​​​​​​​​​i​​​​​ and vi.

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 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.

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 <= 500
  • languages.length == m
  • 1 <= m <= 500
  • 1 <= languages[i].length <= n
  • 1 <= languages[i][j] <= n
  • 1 <= u​​​​​​i < v​​​​​​i <= languages.length
  • 1 <= friendships.length <= 500
  • All tuples (u​​​​​i, v​​​​​​i) are unique
  • languages[i] contains only unique values

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What are the constraints on n, the number of people, and the number of languages each person can speak, and the number of friendships?
  2. Can a person speak zero languages, or can a friendship exist between a person and themselves (u == v in friendships[i])?
  3. If all friends can already communicate without teaching anyone a new language, should I return 0?
  4. Are the languages represented by integers, and if so, what is the range of these language IDs?
  5. If there are multiple sets of people I could teach to achieve the minimum, is any such set acceptable, or is there some criteria for choosing between them?

Brute Force Solution

Approach

The brute force way to solve this problem is like trying every single combination of people to teach until we find one that makes sure everyone can communicate. We will painstakingly explore each possibility to guarantee we find the absolute minimum.

Here's how the algorithm would work step-by-step:

  1. Start by teaching only the first person a language. See if that makes it possible for everyone to talk to each other, considering the languages they already know.
  2. If not, try teaching the first two people a language. Check again if everyone can now communicate.
  3. Keep adding one person at a time to the group we're teaching a language to, and check after each addition if everyone can understand each other.
  4. Once you've tried all the groups of people to teach starting from teaching just one person and working your way up, repeat the process, but this time start by teaching a different single person and again work up.
  5. Do this until you have considered every possible combination of people to teach languages to.
  6. Keep track of how many people you needed to teach in each working combination.
  7. From all these combinations, find the one where you taught the fewest people. That's your answer: the minimum number of people to teach so everyone can understand each other.

Code Implementation

def minimum_people_to_teach(number_of_people, languages):
    minimum_people_needed = number_of_people
    
    for i in range(1 << number_of_people):
        people_to_teach = []
        number_of_people_to_teach = 0
        
        for person_index in range(number_of_people):
            if (i >> person_index) & 1:
                people_to_teach.append(person_index)
                number_of_people_to_teach += 1
                
        # Create a copy to avoid modifying the original list
        updated_languages = [set(lang) for lang in languages]

        for person_index in people_to_teach:
            updated_languages[person_index].add(0)

        can_communicate = True
        for first_person in range(number_of_people):
            can_talk_to_someone = False
            for second_person in range(number_of_people):
                # Check if the people speak a common language
                if len(updated_languages[first_person].intersection(updated_languages[second_person])) > 0:
                    can_talk_to_someone = True
                    break
            if not can_talk_to_someone:
                can_communicate = False
                break

        # Update if this combination results in fewer people taught
        if can_communicate:
            minimum_people_needed = min(minimum_people_needed, number_of_people_to_teach)

    return minimum_people_needed

Big(O) Analysis

Time Complexity
O(2^n * n * m)The algorithm explores all possible subsets of people to teach, which is 2^n where n is the number of people. For each subset, it needs to check if everyone can communicate. This communication check involves iterating through all pairs of people, which is O(n^2). Within the communication check, determining if two people can communicate requires iterating through their language lists, which takes O(m) time, where m is the maximum number of languages any person knows. Therefore, the overall time complexity is O(2^n * n * m).
Space Complexity
O(N)The brute force solution, as described, explores every possible subset of people to teach. In the worst case, to check if a subset allows everyone to communicate, a temporary data structure proportional to the number of people, N, (e.g., a list or set) might be used to track languages known by the taught subset. This structure is recreated for each subset explored. Therefore, the auxiliary space required is O(N), where N is the number of people.

Optimal Solution

Approach

The problem asks us to minimize the number of people we need to teach a new set of languages to, so that everyone in a social network can understand each other. The key is to find which languages are not already understood by everyone directly or indirectly connected to each other. Then we teach those languages to someone already in the social network.

Here's how the algorithm would work step-by-step:

  1. First, figure out who is connected to whom, building a social network map. This map groups people who can already communicate with each other, even if it requires some translation through friends.
  2. Next, for each group of connected people in the social network, check if everyone in that group already shares a common language. If they do, no new teaching is needed for that group.
  3. If a group doesn't have a common language, it means at least one person needs to learn a new one. Since the goal is to minimize the number of people taught, count the number of groups that don't already share a language.
  4. The final count of groups that lack a shared language represents the minimum number of people we need to teach a new language to, ensuring everyone can understand each other within their respective social circles.

Code Implementation

def minimum_teachings(number_of_people, languages): 
    social_network = [set() for _ in range(number_of_people)]

    for person1 in range(number_of_people): 
        for person2 in range(person1 + 1, number_of_people): 
            if any(language in languages[person2] for language in languages[person1]):
                social_network[person1].add(person2)
                social_network[person2].add(person1)

    visited = [False] * number_of_people
    connected_components = []

    def depth_first_search(person, component): 
        visited[person] = True
        component.add(person)
        for neighbor in social_network[person]:
            if not visited[neighbor]:
                depth_first_search(neighbor, component)

    # Build social network map with DFS
    for person in range(number_of_people): 
        if not visited[person]:
            component = set()
            depth_first_search(person, component)
            connected_components.append(component)

    people_to_teach_count = 0
    for component in connected_components: 
        shared_language = False
        if len(component) == 1: 
            continue

        component_languages = set()
        people_in_component = list(component)
        
        # Collect all languages spoken in the component
        for person in people_in_component:
            component_languages.update(languages[person])

        # Check if everyone already shares a language
        for language in component_languages:
            language_is_shared = True
            for person in people_in_component:
                if language not in languages[person]:
                    language_is_shared = False
                    break
            if language_is_shared:
                shared_language = True
                break

        # Increment count if shared language is absent
        if not shared_language: 
            people_to_teach_count += 1

    return people_to_teach_count

Big(O) Analysis

Time Complexity
O(n + m * l)Building the social network (connected components) typically uses Depth-First Search or Breadth-First Search which has a time complexity of O(n) where n is the number of people (nodes). Checking for a common language within each connected component involves iterating through the people in the component (at most n overall) and checking their languages. Let m represent the average size of a connected component (the number of people in the component) and l represent the average number of languages each person speaks. Then, for each connected component, determining if there is a common language can take up to O(m * l) time in the worst case (where we iterate through all languages of each person in that component and compare it to the languages of other people in the component). The total time complexity is dominated by the network traversal to build the social network and then the checks to see if all languages are represented, resulting in O(n + m * l).
Space Complexity
O(N + L)The space complexity is determined by the auxiliary data structures created to represent the social network and the languages spoken. Specifically, a social network map representing the connections between people requires storing adjacency lists, potentially up to N, where N is the number of people. Additionally, when checking for common languages within each connected group, we may need to store intermediate sets or lists of languages, which in the worst case can grow proportionally to L, the total number of languages across all individuals. Therefore, the overall space complexity is O(N + L).

Edge Cases

n = 0, empty languages, empty friendships
How to Handle:
Return 0, as there are no people or friendships to satisfy.
n = 1, languages contains one language, friendships empty
How to Handle:
Return 0, as there's only one person and no friendships to satisfy.
n = 2, friendship exists, but languages are empty for both
How to Handle:
Return 2, as both need to be taught a language to communicate.
n = 2, friendship exists, one person knows a language, other knows none
How to Handle:
Return 1, the person who knows no language needs to be taught.
n = 2, friendship exists, both know completely disjoint languages
How to Handle:
Return 1; teach either person a language the other knows, or teach a common language.
Large n, but all friendships connect into a single connected component, and no one shares a language.
How to Handle:
Return 1; teaching everyone the same language will solve the problem; connected components can be found using DFS/BFS/Union-Find.
Friendships form multiple disconnected components; languages may or may not overlap within or between components.
How to Handle:
Calculate the minimum number of people to teach within each component and sum the results; use DFS/BFS/Union-Find to find components.
All people already speak a common language, but friendships still exist.
How to Handle:
Return 0, as all friendships are already satisfied.