Facebook Pixel

2225. Find Players With Zero or One Losses

MediumArrayHash TableCountingSorting
Leetcode Link

Problem Description

You are given an array matches where each element matches[i] = [winner_i, loser_i] represents a match result - player winner_i defeated player loser_i.

Your task is to return a list answer containing exactly 2 lists:

  • answer[0]: All players who have never lost any matches
  • answer[1]: All players who have lost exactly one match

Both lists should be sorted in increasing order.

Important constraints:

  • Only include players who have played at least one match (either as winner or loser)
  • No two matches will have the same outcome (each match result is unique)

For example, if you have matches like [[1,3], [2,3], [3,6], [5,6], [5,7], [4,5], [4,8], [4,9], [10,4], [10,9]]:

  • Players 1, 2, and 10 never lost β†’ answer[0] = [1, 2, 10]
  • Players 4, 5, 7, and 8 lost exactly once β†’ answer[1] = [4, 5, 7, 8]
  • Players 3, 6, and 9 lost more than once, so they don't appear in either list

The solution uses a hash table to count losses for each player. Winners are added to the hash table with 0 losses if not already present, while losers have their loss count incremented. After processing all matches, players are categorized based on their loss count (0 or 1) and sorted before returning.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to track how many times each player has lost. Since we're looking for players with 0 losses and exactly 1 loss, counting losses is the most direct approach.

When we see a match [winner, loser], we know:

  • The winner didn't lose this match (but might have lost others)
  • The loser definitely lost this match

The tricky part is handling players who only appear as winners. If a player only wins and never appears as a loser in any match, they have 0 losses. This means we need to track all players who participate, not just those who lose.

This leads us to the solution strategy:

  1. For each match, record the winner with 0 losses (if we haven't seen them before)
  2. Increment the loser's loss count
  3. After processing all matches, we'll have a complete record of every player's losses

Why use a hash table? Because we need to:

  • Check if we've seen a player before (O(1) lookup)
  • Update loss counts efficiently (O(1) update)
  • Iterate through all players at the end

The clever part of the solution is using if winner not in cnt: cnt[winner] = 0. This ensures that players who only win get recorded with 0 losses, while not overwriting the loss count if they've already appeared as a loser in a previous match.

Finally, since we need sorted output and we're categorizing players by loss count (0 or 1), we can iterate through the sorted hash table and append players to ans[0] or ans[1] based on their loss count.

Learn more about Sorting patterns.

Solution Approach

The solution uses a hash table (Counter) combined with sorting to efficiently track and categorize players based on their losses.

Step-by-step implementation:

  1. Initialize a Counter: We use Python's Counter() to create a hash table cnt that will store each player's loss count.

  2. Process each match: For each [winner, loser] pair:

    • Check if the winner is in cnt. If not, add them with 0 losses: cnt[winner] = 0
    • Increment the loser's loss count: cnt[loser] += 1

    This ensures every player who participates is recorded, with winners starting at 0 losses unless they've lost before.

  3. Initialize result structure: Create ans = [[], []] where:

    • ans[0] will hold players with 0 losses
    • ans[1] will hold players with 1 loss
  4. Categorize players: Iterate through the sorted hash table using sorted(cnt.items()):

    • The sorting ensures players appear in increasing order
    • For each player x with loss count v:
      • If v < 2 (meaning 0 or 1 losses), append the player to ans[v]
      • This clever indexing automatically puts 0-loss players in ans[0] and 1-loss players in ans[1]
  5. Return the result: The final ans contains two sorted lists as required.

Time Complexity: O(n log n) where n is the number of unique players, due to sorting.

Space Complexity: O(n) for storing the hash table with all unique players.

The elegance of this solution lies in using the loss count as an index (ans[v].append(x)) when v < 2, which automatically categorizes players into the correct list without additional conditional logic.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with matches: [[1,2], [3,2], [1,4], [3,4]]

Step 1: Initialize Counter

  • cnt = {} (empty hash table)

Step 2: Process each match

Match [1,2]:

  • Winner is 1, not in cnt β†’ add cnt[1] = 0
  • Loser is 2 β†’ cnt[2] = 1
  • State: cnt = {1: 0, 2: 1}

Match [3,2]:

  • Winner is 3, not in cnt β†’ add cnt[3] = 0
  • Loser is 2 β†’ cnt[2] = 2 (increment from 1)
  • State: cnt = {1: 0, 2: 2, 3: 0}

Match [1,4]:

  • Winner is 1, already in cnt with 0 losses β†’ no change
  • Loser is 4 β†’ cnt[4] = 1
  • State: cnt = {1: 0, 2: 2, 3: 0, 4: 1}

Match [3,4]:

  • Winner is 3, already in cnt with 0 losses β†’ no change
  • Loser is 4 β†’ cnt[4] = 2 (increment from 1)
  • State: cnt = {1: 0, 2: 2, 3: 0, 4: 2}

Step 3: Categorize players

  • Initialize: ans = [[], []]
  • Sort and iterate through cnt: [(1,0), (2,2), (3,0), (4,2)]

Process player 1 with 0 losses:

  • v = 0 < 2 β†’ append 1 to ans[0]
  • Result: ans = [[1], []]

Process player 2 with 2 losses:

  • v = 2 is not < 2 β†’ skip
  • Result: ans = [[1], []]

Process player 3 with 0 losses:

  • v = 0 < 2 β†’ append 3 to ans[0]
  • Result: ans = [[1, 3], []]

Process player 4 with 2 losses:

  • v = 2 is not < 2 β†’ skip
  • Result: ans = [[1, 3], []]

Final Answer: [[1, 3], []]

  • Players 1 and 3 never lost any matches
  • No players lost exactly one match (both 2 and 4 lost twice)

Solution Implementation

1from typing import List
2from collections import Counter
3
4class Solution:
5    def findWinners(self, matches: List[List[int]]) -> List[List[int]]:
6        # Track the number of losses for each player
7        loss_count = Counter()
8      
9        # Process each match
10        for winner, loser in matches:
11            # Ensure winner is in the dictionary (with 0 losses if never lost)
12            if winner not in loss_count:
13                loss_count[winner] = 0
14            # Increment loss count for the loser
15            loss_count[loser] += 1
16      
17        # Initialize result lists: [players with 0 losses, players with 1 loss]
18        result = [[], []]
19      
20        # Sort players by their ID and categorize based on loss count
21        for player_id, losses in sorted(loss_count.items()):
22            # Players with 0 or 1 loss go into respective lists
23            if losses < 2:
24                result[losses].append(player_id)
25      
26        return result
27
1class Solution {
2    public List<List<Integer>> findWinners(int[][] matches) {
3        // Map to store the number of losses for each player
4        // Key: player ID, Value: number of losses
5        Map<Integer, Integer> lossCount = new HashMap<>();
6      
7        // Process each match
8        for (int[] match : matches) {
9            int winner = match[0];
10            int loser = match[1];
11          
12            // Winner has played but not lost this match (ensure they're in the map with 0 losses if new)
13            lossCount.putIfAbsent(winner, 0);
14          
15            // Loser gets one more loss added to their count
16            lossCount.merge(loser, 1, Integer::sum);
17        }
18      
19        // Initialize result lists: 
20        // Index 0: players with 0 losses
21        // Index 1: players with exactly 1 loss
22        List<List<Integer>> result = List.of(new ArrayList<>(), new ArrayList<>());
23      
24        // Categorize players based on their loss count
25        for (Map.Entry<Integer, Integer> entry : lossCount.entrySet()) {
26            int player = entry.getKey();
27            int losses = entry.getValue();
28          
29            // Add players with 0 or 1 loss to respective lists
30            if (losses < 2) {
31                result.get(losses).add(player);
32            }
33        }
34      
35        // Sort both lists in ascending order as required
36        Collections.sort(result.get(0));
37        Collections.sort(result.get(1));
38      
39        return result;
40    }
41}
42
1class Solution {
2public:
3    vector<vector<int>> findWinners(vector<vector<int>>& matches) {
4        // Map to track loss count for each player
5        // Key: player ID, Value: number of losses
6        map<int, int> lossCount;
7      
8        // Process each match
9        for (auto& match : matches) {
10            int winner = match[0];
11            int loser = match[1];
12          
13            // Initialize winner with 0 losses if not already present
14            if (!lossCount.contains(winner)) {
15                lossCount[winner] = 0;
16            }
17          
18            // Increment loss count for the loser
19            ++lossCount[loser];
20        }
21      
22        // Initialize result with two empty lists
23        // result[0]: players with 0 losses
24        // result[1]: players with exactly 1 loss
25        vector<vector<int>> result(2);
26      
27        // Categorize players based on their loss count
28        for (auto& [playerId, losses] : lossCount) {
29            if (losses < 2) {
30                // Add to appropriate list (0 losses or 1 loss)
31                result[losses].push_back(playerId);
32            }
33        }
34      
35        return result;
36    }
37};
38
1/**
2 * Finds players with 0 losses and players with exactly 1 loss from match results
3 * @param matches - Array of match results where matches[i] = [winner, loser]
4 * @returns Array containing two arrays: [playersWithNoLosses, playersWithOneLoss]
5 */
6function findWinners(matches: number[][]): number[][] {
7    // Map to track the number of losses for each player
8    const lossCount: Map<number, number> = new Map();
9  
10    // Process each match to count losses
11    for (const [winner, loser] of matches) {
12        // Initialize winner with 0 losses if not already tracked
13        if (!lossCount.has(winner)) {
14            lossCount.set(winner, 0);
15        }
16        // Increment loss count for the loser
17        lossCount.set(loser, (lossCount.get(loser) || 0) + 1);
18    }
19  
20    // Initialize result arrays: [playersWithNoLosses, playersWithOneLoss]
21    const result: number[][] = [[], []];
22  
23    // Categorize players based on their loss count
24    for (const [player, losses] of lossCount) {
25        // Add players with 0 or 1 loss to appropriate array
26        if (losses < 2) {
27            result[losses].push(player);
28        }
29    }
30  
31    // Sort both arrays in ascending order
32    result[0].sort((a, b) => a - b);
33    result[1].sort((a, b) => a - b);
34  
35    return result;
36}
37

Time and Space Complexity

The time complexity is O(n Γ— log n), where n is the number of matches.

  • Iterating through all matches to build the counter takes O(n) time, where each match contributes two operations (one for the winner check/initialization and one for the loser increment).
  • The sorted(cnt.items()) operation sorts the dictionary items by player ID. In the worst case, we could have up to 2n unique players (if every match has two unique players), so sorting takes O(2n Γ— log(2n)) = O(n Γ— log n) time.
  • The final loop to populate the answer lists takes O(k) time where k is the number of unique players, which is at most 2n.
  • Overall: O(n) + O(n Γ— log n) + O(n) = O(n Γ— log n)

The space complexity is O(n).

  • The cnt Counter stores at most 2n unique players (in the worst case where all players in all matches are unique), which requires O(n) space.
  • The ans list stores the players who lost 0 or 1 match, which in the worst case could be all 2n players, requiring O(n) space.
  • The sorting operation may use additional O(n) space depending on the implementation.
  • Overall: O(n) space complexity

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Forgetting to Track Winners Who Never Lost

The Problem: A common mistake is only adding players to the hash table when they lose a match. This would miss players who only appear as winners and never as losers.

# INCORRECT approach:
loss_count = Counter()
for winner, loser in matches:
    loss_count[loser] += 1  # Only tracking losers
# This misses winners who never lost!

The Solution: Always check if a winner exists in the hash table. If not, initialize them with 0 losses:

# CORRECT approach:
for winner, loser in matches:
    if winner not in loss_count:
        loss_count[winner] = 0  # Explicitly track winners
    loss_count[loser] += 1

Pitfall 2: Not Sorting the Results

The Problem: The problem requires both lists to be sorted in increasing order. Simply iterating through the hash table without sorting would produce unsorted results since hash tables don't maintain order.

# INCORRECT - produces unsorted results:
for player_id, losses in loss_count.items():  # No sorting!
    if losses < 2:
        result[losses].append(player_id)

The Solution: Sort the items before categorizing:

# CORRECT - ensures sorted output:
for player_id, losses in sorted(loss_count.items()):
    if losses < 2:
        result[losses].append(player_id)

Pitfall 3: Including Players with More Than One Loss

The Problem: Accidentally including players who lost 2 or more matches in the second list:

# INCORRECT logic:
if losses == 0:
    result[0].append(player_id)
else:  # This would include players with 2+ losses!
    result[1].append(player_id)

The Solution: Use the condition losses < 2 to only include players with 0 or 1 loss:

# CORRECT - filters out players with 2+ losses:
if losses < 2:
    result[losses].append(player_id)

Pitfall 4: Using Default Dictionary Incorrectly

The Problem: Using defaultdict(int) might seem convenient, but you still need to ensure winners are added:

# RISKY approach with defaultdict:
from collections import defaultdict
loss_count = defaultdict(int)
for winner, loser in matches:
    loss_count[loser] += 1
# Winners aren't explicitly added, relying on iteration over matches

The Solution: Even with defaultdict, explicitly handle winners to ensure they're tracked:

loss_count = defaultdict(int)
for winner, loser in matches:
    loss_count[winner] += 0  # Ensures winner is in the dict
    loss_count[loser] += 1
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm is best for finding the shortest distance between two points in an unweighted graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More