Facebook Pixel

2682. Find the Losers of the Circular Game

EasyArrayHash TableSimulation
Leetcode Link

Problem Description

You have n friends sitting in a circle, numbered from 1 to n in clockwise order. They're playing a ball-passing game with specific rules.

The game starts with friend 1 receiving the ball. Then:

  • On the 1st pass, friend 1 passes the ball to the friend who is k positions away clockwise
  • On the 2nd pass, that friend passes the ball to someone 2 * k positions away clockwise
  • On the 3rd pass, the ball goes to someone 3 * k positions away clockwise
  • This pattern continues, where on the i-th pass, the ball travels i * k positions clockwise

The game ends when any friend receives the ball for the second time.

Your task is to find all the "losers" - friends who never received the ball during the entire game. Return their numbers in ascending order.

For example, if n = 5 and k = 2:

  • Friend 1 starts with the ball
  • Pass 1: Friend 1 passes 1 * 2 = 2 positions to friend 3
  • Pass 2: Friend 3 passes 2 * 2 = 4 positions to friend 2 (wrapping around the circle)
  • Pass 3: Friend 2 passes 3 * 2 = 6 positions to friend 3 (wrapping around)
  • Friend 3 receives the ball for the second time, so the game ends
  • Friends 4 and 5 never received the ball, so they are the losers

The solution simulates this process using an array to track which friends have received the ball, updating the current position by (current + pass_number * k) % n after each pass until someone receives the ball twice.

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

Intuition

The key insight is that this is a simulation problem where we need to track who receives the ball until someone gets it twice. Since friends are arranged in a circle, we can use modular arithmetic to handle the wraparound effect.

Think of the circle as an array where index 0 represents friend 1, index 1 represents friend 2, and so on. When we pass the ball i * k steps clockwise, we can calculate the next friend's position using (current_position + i * k) % n. The modulo operation naturally handles the circular nature - if we go past the last friend, we wrap back to the beginning.

We need to track two things:

  1. Which friends have received the ball (using a boolean array)
  2. When to stop (when someone receives the ball for the second time)

The simulation approach is natural here because:

  • The game has a clear termination condition (someone gets the ball twice)
  • We need to track the exact path of the ball to know who never receives it
  • The number of passes is bounded since there are only n friends, and eventually the ball must return to someone who already had it

Starting from friend 1 (index 0), we keep passing the ball according to the rule: on pass p, move p * k positions forward. We mark each friend who receives the ball in our tracking array. When we encounter a friend who's already marked (already received the ball), we stop. Everyone still unmarked at this point never got the ball - they're our losers.

The beauty of this approach is its directness - we're literally playing out the game step by step, which makes the solution both intuitive and correct.

Solution Approach

The implementation uses a simulation approach with a boolean array to track which friends have received the ball.

Data Structures:

  • A boolean array vis of size n to track whether each friend has received the ball. Initially all values are False.
  • Two variables: i for the current friend's index (0-based) and p for the current pass number.

Algorithm Steps:

  1. Initialize the tracking system:

    • Create vis = [False] * n to track ball reception
    • Set i = 0 (friend 1 at index 0 starts with the ball)
    • Set p = 1 (the first pass will be 1 * k steps)
  2. Simulate the game with a while loop:

    while not vis[i]:
        vis[i] = True           # Mark current friend as having received the ball
        i = (i + p * k) % n     # Calculate next friend's position
        p += 1                  # Increment pass counter for next iteration

    The loop continues until we encounter a friend who has already received the ball (vis[i] is True).

  3. Identify the losers: After the simulation ends, iterate through the vis array:

    return [i + 1 for i in range(n) if not vis[i]]

    For each index i where vis[i] is False, that friend never received the ball. We add i + 1 to convert from 0-based indexing to the 1-based friend numbering.

Key Implementation Details:

  • The modulo operation (i + p * k) % n handles the circular arrangement perfectly. If the calculated position exceeds n-1, it wraps around to the beginning.
  • The pass multiplier p starts at 1 and increments after each pass, ensuring that pass number p moves the ball p * k positions.
  • The condition not vis[i] in the while loop ensures we stop immediately when reaching a friend who has already received the ball.
  • The list comprehension at the end efficiently collects all losers in ascending order (since we iterate from 0 to n-1).

This solution has a time complexity of O(n) in the worst case (when all friends receive the ball exactly once before it returns to someone) and space complexity of O(n) for the tracking array.

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 trace through a small example with n = 5 friends and k = 2.

Initial Setup:

  • Friends are numbered 1 to 5, sitting in a circle
  • We create a tracking array vis = [False, False, False, False, False] (indices 0-4 represent friends 1-5)
  • Friend 1 (index 0) starts with the ball
  • Variables: i = 0 (current position), p = 1 (pass number)

Pass 1:

  • Check vis[0]: It's False, so friend 1 hasn't had the ball yet
  • Mark vis[0] = Truevis = [True, False, False, False, False]
  • Calculate next position: (0 + 1 * 2) % 5 = 2
  • Update: i = 2, p = 2
  • Ball goes from friend 1 to friend 3

Pass 2:

  • Check vis[2]: It's False, so friend 3 hasn't had the ball yet
  • Mark vis[2] = Truevis = [True, False, True, False, False]
  • Calculate next position: (2 + 2 * 2) % 5 = 6 % 5 = 1
  • Update: i = 1, p = 3
  • Ball goes from friend 3 to friend 2

Pass 3:

  • Check vis[1]: It's False, so friend 2 hasn't had the ball yet
  • Mark vis[1] = Truevis = [True, True, True, False, False]
  • Calculate next position: (1 + 3 * 2) % 5 = 7 % 5 = 2
  • Update: i = 2, p = 4
  • Ball goes from friend 2 to friend 3

Game Ends:

  • Check vis[2]: It's True - friend 3 already had the ball!
  • The while loop exits

Finding Losers:

  • Check each position in vis:
    • Index 0 (True): Friend 1 received the ball
    • Index 1 (True): Friend 2 received the ball
    • Index 2 (True): Friend 3 received the ball
    • Index 3 (False): Friend 4 never received the ball → loser
    • Index 4 (False): Friend 5 never received the ball → loser
  • Return [4, 5] (converting indices 3, 4 to friend numbers)

The ball's path was: Friend 1 → Friend 3 → Friend 2 → Friend 3 (game ends). Friends 4 and 5 never touched the ball.

Solution Implementation

1class Solution:
2    def circularGameLosers(self, n: int, k: int) -> List[int]:
3        # Track which positions have been visited
4        visited = [False] * n
5      
6        # Initialize current position and step multiplier
7        current_position = 0
8        step_multiplier = 1
9      
10        # Continue until we visit a position that's already been visited
11        while not visited[current_position]:
12            # Mark current position as visited
13            visited[current_position] = True
14          
15            # Calculate next position using circular indexing
16            # The step size increases by k each time (1*k, 2*k, 3*k, ...)
17            current_position = (current_position + step_multiplier * k) % n
18          
19            # Increment the step multiplier for next iteration
20            step_multiplier += 1
21      
22        # Return all positions that were never visited (losers)
23        # Convert 0-indexed positions to 1-indexed for the result
24        return [position + 1 for position in range(n) if not visited[position]]
25
1class Solution {
2    public int[] circularGameLosers(int n, int k) {
3        // Track which friends have received the ball
4        boolean[] hasReceivedBall = new boolean[n];
5      
6        // Count how many friends have received the ball
7        int friendsWithBall = 0;
8      
9        // Simulate the game: currentIndex tracks position, passNumber tracks which pass we're on
10        int currentIndex = 0;
11        int passNumber = 1;
12      
13        // Continue until someone receives the ball for the second time
14        while (!hasReceivedBall[currentIndex]) {
15            // Mark current friend as having received the ball
16            hasReceivedBall[currentIndex] = true;
17            friendsWithBall++;
18          
19            // Calculate next friend's position
20            // The ball moves by passNumber * k positions
21            currentIndex = (currentIndex + passNumber * k) % n;
22            passNumber++;
23        }
24      
25        // Create result array for friends who never received the ball
26        int[] losers = new int[n - friendsWithBall];
27        int resultIndex = 0;
28      
29        // Find all friends who never received the ball
30        for (int i = 0; i < n; i++) {
31            if (!hasReceivedBall[i]) {
32                // Convert 0-indexed to 1-indexed friend number
33                losers[resultIndex] = i + 1;
34                resultIndex++;
35            }
36        }
37      
38        return losers;
39    }
40}
41
1class Solution {
2public:
3    vector<int> circularGameLosers(int n, int k) {
4        // Create a visited array to track which players received the ball
5        bool visited[n];
6        memset(visited, false, sizeof(visited));
7      
8        // Simulate the ball passing game
9        // currentIndex: current player position (0-indexed)
10        // passCount: number of passes made so far (starts at 1)
11        int currentIndex = 0;
12        int passCount = 1;
13      
14        while (!visited[currentIndex]) {
15            // Mark current player as having received the ball
16            visited[currentIndex] = true;
17          
18            // Calculate next player's position
19            // The ball moves passCount * k positions forward
20            currentIndex = (currentIndex + passCount * k) % n;
21          
22            // Increment pass count for next iteration
23            passCount++;
24        }
25      
26        // Collect all players who never received the ball
27        vector<int> losers;
28        for (int i = 0; i < n; ++i) {
29            if (!visited[i]) {
30                // Convert to 1-indexed and add to result
31                losers.push_back(i + 1);
32            }
33        }
34      
35        return losers;
36    }
37};
38
1/**
2 * Finds all players who never receive the ball in a circular game.
3 * 
4 * Game rules:
5 * - n players stand in a circle, numbered from 1 to n
6 * - The ball starts with player 1
7 * - In round p, the current player passes the ball to the player p*k positions away (clockwise)
8 * - The game continues until a player receives the ball for the second time
9 * 
10 * @param n - The number of players in the game
11 * @param k - The step multiplier for each round
12 * @returns An array of player numbers (1-indexed) who never received the ball
13 */
14function circularGameLosers(n: number, k: number): number[] {
15    // Track which players have received the ball (0-indexed)
16    const hasReceivedBall: boolean[] = new Array(n).fill(false);
17  
18    // Result array to store losers (players who never received the ball)
19    const losers: number[] = [];
20  
21    // Simulate the game
22    let currentPlayerIndex: number = 0;  // Start with player at index 0 (player 1)
23    let roundNumber: number = 1;         // Current round number
24  
25    // Continue until a player receives the ball for the second time
26    while (!hasReceivedBall[currentPlayerIndex]) {
27        // Mark current player as having received the ball
28        hasReceivedBall[currentPlayerIndex] = true;
29      
30        // Calculate next player's index
31        // In round p, pass the ball p*k positions clockwise
32        currentPlayerIndex = (currentPlayerIndex + roundNumber * k) % n;
33      
34        // Move to next round
35        roundNumber++;
36    }
37  
38    // Find all players who never received the ball
39    for (let i: number = 0; i < hasReceivedBall.length; i++) {
40        if (!hasReceivedBall[i]) {
41            // Add player number (1-indexed) to result
42            losers.push(i + 1);
43        }
44    }
45  
46    return losers;
47}
48

Time and Space Complexity

The time complexity is O(n), and the space complexity is O(n).

Time Complexity Analysis:

  • The while loop continues until we revisit a position that has already been visited (when vis[i] becomes True)
  • In the worst case, we visit all n positions exactly once before revisiting a position
  • Each iteration of the while loop performs constant time operations: array access, arithmetic operations, and modulo operation
  • The list comprehension at the end iterates through all n positions once to collect unvisited friends
  • Therefore, the overall time complexity is O(n) + O(n) = O(n)

Space Complexity Analysis:

  • The vis array stores boolean values for n positions, requiring O(n) space
  • The output list in the worst case could contain up to n-1 elements (if only one friend receives the ball), which is O(n) space
  • Other variables (i, p) use constant space O(1)
  • Therefore, the total space complexity is O(n)

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

Common Pitfalls

1. Off-by-One Error in Index Conversion

A frequent mistake is forgetting to convert between 0-based array indices and 1-based friend numbers. This can happen in two places:

Pitfall Example:

# Wrong: Returning 0-based indices instead of friend numbers
return [i for i in range(n) if not visited[i]]  # Returns [3, 4] instead of [4, 5]

Solution: Always remember to add 1 when converting from array indices to friend numbers:

return [i + 1 for i in range(n) if not visited[i]]  # Correctly returns [4, 5]

2. Incorrect Pass Counter Initialization

Starting the pass counter at 0 instead of 1 causes the first pass to move 0 positions, which violates the problem requirements.

Pitfall Example:

step_multiplier = 0  # Wrong: First pass would be 0 * k = 0 positions
while not visited[current_position]:
    visited[current_position] = True
    current_position = (current_position + step_multiplier * k) % n
    step_multiplier += 1

Solution: Initialize the step multiplier to 1, or update it before calculating the next position:

# Option 1: Initialize to 1
step_multiplier = 1

# Option 2: Increment before calculation
while not visited[current_position]:
    visited[current_position] = True
    step_multiplier += 1
    current_position = (current_position + step_multiplier * k) % n

3. Marking Position After Movement

Marking the position as visited after calculating the next position leads to incorrect tracking of who received the ball.

Pitfall Example:

while not visited[current_position]:
    current_position = (current_position + step_multiplier * k) % n
    visited[current_position] = True  # Wrong: Marks next position, not current
    step_multiplier += 1

This marks the destination of each pass but never marks the starting position (friend 1), leading to friend 1 being incorrectly identified as a loser.

Solution: Always mark the current position before moving to the next:

while not visited[current_position]:
    visited[current_position] = True  # Mark current position first
    current_position = (current_position + step_multiplier * k) % n
    step_multiplier += 1

4. Edge Case: k = 0

When k = 0, the ball never moves from friend 1, creating an infinite loop scenario that the basic implementation might not handle correctly.

Solution: Add a special case check or ensure the problem constraints guarantee k > 0:

if k == 0:
    return list(range(2, n + 1))  # Everyone except friend 1 is a loser
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How does quick sort divide the problem into subproblems?


Recommended Readings

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

Load More