Taro Logo

Maximum Matching of Players With Trainers

Medium
Asked by:
Profile picture
16 views
Topics:
ArraysGreedy AlgorithmsTwo Pointers

You are given a 0-indexed integer array players, where players[i] represents the ability of the ith player. You are also given a 0-indexed integer array trainers, where trainers[j] represents the training capacity of the jth trainer.

The ith player can match with the jth trainer if the player's ability is less than or equal to the trainer's training capacity. Additionally, the ith player can be matched with at most one trainer, and the jth trainer can be matched with at most one player.

Return the maximum number of matchings between players and trainers that satisfy these conditions.

Example 1:

Input: players = [4,7,9], trainers = [8,2,5,8]
Output: 2
Explanation:
One of the ways we can form two matchings is as follows:
- players[0] can be matched with trainers[0] since 4 <= 8.
- players[1] can be matched with trainers[3] since 7 <= 8.
It can be proven that 2 is the maximum number of matchings that can be formed.

Example 2:

Input: players = [1,1,1], trainers = [10]
Output: 1
Explanation:
The trainer can be matched with any of the 3 players.
Each player can only be matched with one trainer, so the maximum answer is 1.

Constraints:

  • 1 <= players.length, trainers.length <= 105
  • 1 <= players[i], trainers[j] <= 109

Note: This question is the same as 445: Assign Cookies.

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 possible ranges for the players' abilities and the trainers' capacities? Can these values be negative or zero?
  2. Can the lists of players and trainers be empty? If so, what should I return?
  3. Are the player abilities and trainer capacities guaranteed to be unique, or can there be duplicates, and how should I handle them?
  4. If there are multiple valid matchings that maximize the number of matched players, is any one of them acceptable?
  5. What is the expected data type of the return value? Should I return the number of matched players, or some other representation of the matching?

Brute Force Solution

Approach

We want to find the largest number of player-trainer pairs where the player's skill is not greater than the trainer's ability. The brute force approach is to consider every possible combination of players and trainers to see if they can be matched.

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

  1. Start by looking at the first player.
  2. Consider matching this player with the first trainer, then the second trainer, and so on, trying every trainer.
  3. For each possible match, see if the player's skill is less than or equal to the trainer's ability. If not, this match isn't valid.
  4. If it's a valid match, mark those two as paired up, and then move on to the next player and repeat the process with the remaining trainers.
  5. Remember that once a trainer is matched, you can't use them again for another player in the same combination.
  6. Repeat this process, starting with the first player matched with the second trainer, then the third trainer, and so on.
  7. Continue this until you have explored every possible combination of player-trainer pairings.
  8. For each combination, count how many valid player-trainer matches were made.
  9. After checking every possible combination, select the one that resulted in the maximum number of matches.

Code Implementation

def maximum_matching_brute_force(players, trainers):
    maximum_matches = 0

    def find_maximum_matches(current_player_index, used_trainers, current_matches):
        nonlocal maximum_matches

        # If we've considered all players, update the maximum matches
        if current_player_index == len(players):
            maximum_matches = max(maximum_matches, current_matches)
            return

        # Try matching the current player with each available trainer
        for trainer_index in range(len(trainers)):
            if trainer_index not in used_trainers:
                # Check if the player's skill is less than or equal to the trainer's ability
                if players[current_player_index] <= trainers[trainer_index]:

                    # If it's a valid match, recursively explore further matches
                    find_maximum_matches(
                        current_player_index + 1,
                        used_trainers | {trainer_index},
                        current_matches + 1,
                    )

        # If the current player cannot be matched with any trainer,
        # move on to the next player without increasing the match count
        find_maximum_matches(
            current_player_index + 1,
            used_trainers,
            current_matches,
        )

    find_maximum_matches(0, set(), 0)

    #Return the maximum number of matches found
    return maximum_matches

Big(O) Analysis

Time Complexity
O(n!)The approach explores all possible combinations of matching players to trainers. With 'n' players and 'n' trainers, the number of possible combinations resembles permutations. In the worst-case scenario, each player could potentially be matched with any of the trainers. This leads to a factorial relationship for the number of combinations to explore, where we would need to evaluate every possible pairing arrangement. Therefore, the time complexity grows factorially with the input size, resulting in a runtime of O(n!).
Space Complexity
O(N!)The described brute-force approach explores every possible combination of player-trainer pairings. The depth of recursion can be thought of as the number of players. Each call potentially explores pairing the current player with each of the remaining trainers. The implicit data structure is the call stack that stores information about each combination being explored and the matched status of the trainers, which in the worst case results in exponential space usage related to permutations of size N, where N represents the smaller of the player count and trainer count. Therefore, the space complexity is O(N!).

Optimal Solution

Approach

The best way to match players and trainers efficiently is to first organize them by skill level. Then, we'll go through the players one by one, finding the smallest suitable trainer for each, ensuring we don't reuse trainers.

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

  1. First, sort both the players and the trainers by their skill level (or size).
  2. Start with the least skilled player.
  3. Find the first trainer in the sorted trainer list who can train this player (i.e., the trainer's skill level is greater than or equal to the player's).
  4. If a suitable trainer is found, match them and remove the trainer from the list of available trainers so they aren't used again.
  5. If no suitable trainer is found, this player cannot be matched.
  6. Repeat steps 3-5 for each remaining player.
  7. The total number of successful matches represents the maximum possible matches.

Code Implementation

def maximum_matching(players, trainers): 
    players.sort()
    trainers.sort()
    
    matches_count = 0
    trainer_index = 0

    # Iterate through each player to find a suitable trainer
    for player_skill in players:
        # Find the first trainer that meets the player's skill req.
        while trainer_index < len(trainers) and \
                trainers[trainer_index] < player_skill:
            trainer_index += 1

        # If a suitable trainer is found, increment matches
        if trainer_index < len(trainers):
            matches_count += 1
            trainer_index += 1

    return matches_count

Big(O) Analysis

Time Complexity
O(n log n)The dominant operations affecting the time complexity are sorting the players and trainers and iterating through them to find matches. Sorting the players and trainers each takes O(n log n) time, where n is the number of players or trainers (assuming they are of similar size). The matching process iterates through the sorted lists, potentially requiring a linear scan in the worst case. Since sorting dominates the linear scan, the overall time complexity is O(n log n).
Space Complexity
O(1)The provided solution sorts the input lists in place. After sorting, the algorithm iterates through the players and trainers, using a single index to track the current trainer. The only auxiliary space used is for this index variable, which takes constant space regardless of the number of players or trainers. Since the space used does not scale with the input size N (number of players and trainers), the space complexity is O(1).

Edge Cases

Null or empty players array
How to Handle:
Return 0 if either the players or trainers array is null or empty, indicating no matchings possible.
Null or empty trainers array
How to Handle:
Return 0 if either the players or trainers array is null or empty, indicating no matchings possible.
Players array with a single element
How to Handle:
If the trainers array contains an element greater than or equal to the player, return 1, otherwise 0.
Trainers array with a single element
How to Handle:
If the players array contains an element less than or equal to the trainer, return 1, otherwise 0.
Players and Trainers arrays contain identical values
How to Handle:
The greedy matching approach will correctly identify the maximum number of matches.
Players array with very large sizes, trainers array with smaller sizes, and no matching possible
How to Handle:
The sorting-based approach still functions, but the runtime is dominated by sorting and iterating to the end results in 0.
Integer overflow when comparing extreme values
How to Handle:
Using a language with arbitrary-precision integers or explicitly check for potential overflows before comparison
Both arrays are very large
How to Handle:
Ensure the sorting algorithm used (if any) has acceptable time complexity (e.g., O(n log n) or O(m log m)) to avoid timeouts.