2682. Find the Losers of the Circular Game
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 isk
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 travelsi * 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.
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:
- Which friends have received the ball (using a boolean array)
- 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 sizen
to track whether each friend has received the ball. Initially all values areFalse
. - Two variables:
i
for the current friend's index (0-based) andp
for the current pass number.
Algorithm Steps:
-
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 be1 * k
steps)
- Create
-
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]
isTrue
). -
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
wherevis[i]
isFalse
, that friend never received the ball. We addi + 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 exceedsn-1
, it wraps around to the beginning. - The pass multiplier
p
starts at 1 and increments after each pass, ensuring that pass numberp
moves the ballp * 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 EvaluatorExample 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'sFalse
, so friend 1 hasn't had the ball yet - Mark
vis[0] = True
→vis = [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'sFalse
, so friend 3 hasn't had the ball yet - Mark
vis[2] = True
→vis = [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'sFalse
, so friend 2 hasn't had the ball yet - Mark
vis[1] = True
→vis = [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'sTrue
- 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
- Index 0 (
- 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]
becomesTrue
) - 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 forn
positions, requiringO(n)
space - The output list in the worst case could contain up to
n-1
elements (if only one friend receives the ball), which isO(n)
space - Other variables (
i
,p
) use constant spaceO(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
How does quick sort divide the problem into subproblems?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!