2682. Find the Losers of the Circular Game
Problem Description
In this game involving n
friends sitting in a circle, the play involves passing a ball around in a sequential and expanding pattern. The central rule is that with each turn, the distance in steps between the passer and the receiver increases linearly by k
. That is, during the first turn, the ball is passed k
steps away; on the second turn, it's 2 * k
steps away, then 3 * k
steps away on the third turn, and so on.
The key point is that the counting is cyclic and wraps around the circle. Once the count reaches the last friend, it continues from the first friend again. This cycle continues until a friend receives the ball for the second time, which signals the end of the game. The friends who never got the ball even once are the losers of the game.
The objective of this problem is to determine which friends lose the game, i.e., never receive the ball. This list of losers should be returned in ascending order of their numbered positions.
Intuition
To approach the solution, we can simulate the process of the game since the game’s rules are straightforward. If we follow the ball pass-by-pass, we can record who gets the ball and when the game ends.
Considering we are simulating the game's process, we have to track each friend who has received the ball. We can use an array, vis
, of the same length as the number of friends (n
). Each position in the array corresponds to a friend, where a value of True
at an index means the friend at that position has received the ball, and False
means they have not.
We keep two variables: i
, which keeps the position of the current ball-holder, and p
, which keeps the count of passes made thus far. Starting at position 0
(the 1st
friend), with each turn, we mark the current position as visited (True
), then move i
by p * k
steps forward (using modulo %
operator to wrap around the circle), and increment p
by 1 for the next pass. This continues until we return to a previously visited position, which means a friend got the ball for the second time, and the game ends.
After the game ends, we know who has had at least one turn with the ball. The remaining unvisited positions in the vis
array correspond to friends who never got the ball—these are our losers. We output their corresponding numbers (incrementing by 1 since friend numbering is 1-based), filtered in ascending order.
The presented solution encapsulates this simulation in the circularGameLosers
method and produces the result efficiently.
Solution Approach
The solution uses an elementary algorithm that is a direct implementation of the rules of the game. It simulates the passing of the ball amongst friends in a circle. The primary data structure is a list in Python (vis
) used to keep track of which friends have received the ball.
Here is a step-by-step walkthrough of the algorithm:
-
Initialize the
vis
list withFalse
values since no one has received the ball initially.vis = [False] * n
-
Create two variables:
i
to represent the current position (starting from0
, which corresponds to the1st
friend) andp
to represent thep
th pass (starting from1
).i, p = 0, 1
-
Use a
while
loop to keep passing the ball until a friend receives it a second time, which is indicated when we hit aTrue
in thevis
list at positioni
. -
Inside the loop, mark the current position
i
as visited (True
) invis
.vis[i] = True
-
Calculate the next position
i
to pass the ball to using the formula(i + p * k) % n
, which accounts for the cyclic nature of passing the ball around the circle.i = (i + p * k) % n
-
Increment
p
by1
to reflect the rules of the game for the next turn (p
increments linearly each turn). -
When the loop ends (upon a second receipt), iterate through the
vis
list to collect the indices (friend numbers) where the value is stillFalse
, which means these friends did not receive the ball even once. -
For each unvisited index
i
, returni + 1
because friend numbering is 1-based, not 0-based. Produce the final output list using a list comprehension.return [i + 1 for i in range(n) if not vis[i]]
The algorithm is a straightforward simulation and manages to keep time complexity at O(n) since each friend is visited at most once (every friend gets the ball at most once before the game ends). The use of modulo %
for cyclic counting and a list comprehension for filtering out the losers contribute to the solution's conciseness and efficiency.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's imagine a game with n = 5
friends sitting in a circle, and we'll use k = 2
to set the passing sequence. So, in this setup, the ball will be passed in increasing steps of 2 each turn.
-
Let's start by creating the
vis
array to track who has received the ball, initializing all toFalse
.vis = [False, False, False, False, False]
-
We'll start the pass from the
1st
friend (indexed as0
) and initiate our pass countp
at1
.i, p = 0, 1 # Starting from the 1st friend
-
As per the rules, the
1st
friend passes the ball2
steps away sincep * k = 1 * 2 = 2
. Hence, the ball goes to the3rd
friend (positioni = (0 + 1*2) % 5 = 2
).vis[0] = True # Mark the 1st friend as having received the ball i = (0 + 1 * 2) % 5 # Ball goes to the 3rd friend p += 1 # Increment pass counter for the next turn
Now,
vis = [True, False, False, False, False]
-
Now on the second turn,
p
has incremented to2
, so the3rd
friend will pass it4
steps ahead, which loops back around and lands on the2nd
friend (positioni = (2 + 2*2) % 5 = 1
).vis[2] = True # Mark the 3rd friend as having received the ball i = (2 + 2 * 2) % 5 # Ball goes to the 2nd friend p += 1
Now,
vis = [True, False, True, False, False]
-
On the third turn, we have
p = 3
, the ball is passed to(1 + 3*2) % 5 = 0
, which is back to the1st
friend. But since the1st
friend has already received the ball once (vis[0] = True
), the game ends here. -
At this point, we can see from our
vis
array who hasn't received the ball:vis = [True, False, True, False, False]
The unvisited positions correspond to the
2nd
,4th
, and5th
friends. -
We then return the list of losers (those who never received the ball), incrementing the index by one for the correct numbering:
losers = [i + 1 for i in range(5) if not vis[i]] # [2, 4, 5]
In this example, the friends who lose this game are the 2nd
, 4th
, and 5th
friends.
Solution Implementation
1from typing import List
2
3class Solution:
4 def circularGameLosers(self, n: int, k: int) -> List[int]:
5 # Create a list to keep track of visited positions
6 visited = [False] * n
7
8 # Initialize index and step count
9 current_index, step = 0, 1
10
11 # Loop and mark visited positions
12 while not visited[current_index]:
13 visited[current_index] = True
14
15 # Calculate the next index by moving 'k' steps forward
16 # 'step' increases to simulate the change in the total number of players
17 current_index = (current_index + step * k) % n
18 step += 1
19
20 # Return a list of non-visited indices (losers)
21 # Note: compensate for 0-indexed list by adding 1 to each index
22 return [index + 1 for index in range(n) if not visited[index]]
23
1class Solution {
2
3 // Function that determines the positions that lose in the circular game
4 public int[] circularGameLosers(int n, int k) {
5 // Create an array to mark visited (eliminated) positions
6 boolean[] visited = new boolean[n];
7 int count = 0; // Count the number of visited (eliminated) positions
8 // Loop through the array, marking off eliminated positions
9 for (int index = 0, step = 1; !visited[index]; ++step) {
10 visited[index] = true; // Mark the current position as visited
11 ++count; // Increase the count of visited positions
12 // Calculate the next index based on the current index, step number and k
13 // Use modulo n to wrap around the circle
14 index = (index + step * k) % n;
15 }
16
17 // Initialize an array to store the positions that did not lose (were not visited)
18 int[] losers = new int[n - count];
19 // Fill the array with the positions of those who did not lose
20 for (int i = 0, j = 0; i < n; ++i) {
21 if (!visited[i]) {
22 losers[j++] = i + 1; // Store the position (1-indexed) in the losers array
23 }
24 }
25
26 return losers; // Return the array with the positions that lost in the game
27 }
28}
29
1#include <vector>
2#include <cstring>
3
4class Solution {
5public:
6 // Function simulates a circular game and returns a vector of losers' positions (1-indexed)
7 std::vector<int> circularGameLosers(int n, int k) {
8 std::vector<bool> visited(n, false); // Vector to keep track of visited positions
9
10 // Starting from the first position, mark visited positions
11 for (int currentPosition = 0, stepMultiplier = 1;
12 !visited[currentPosition];
13 ++stepMultiplier) {
14 visited[currentPosition] = true;
15
16 // Compute the next position considering the steps taken
17 currentPosition = (currentPosition + stepMultiplier * k) % n;
18 }
19
20 std::vector<int> losers; // Initialize a vector to hold the losers
21 // Add unvisited positions (losers) to the vector. Positions are incremented by
22 // 1 because the problem is likely using 1-indexed positions.
23 for (int i = 0; i < n; ++i) {
24 if (!visited[i]) {
25 losers.push_back(i + 1);
26 }
27 }
28
29 return losers; // Return the vector of losers
30 }
31};
32
1function circularGameLosers(numPlayers: number, skipCount: number): number[] {
2 // Create an array to keep track of the players who have been eliminated.
3 const isEliminated = new Array(numPlayers).fill(false);
4
5 // An array to hold the losers of the game, i.e., the eliminated players.
6 const losers: number[] = [];
7
8 // Loop to eliminate players until there is a last player standing.
9 // 'currentIndex' represents the current player in the loop, 'pass' increments each round to mimic the circular nature.
10 for (let currentIndex = 0, pass = 1; !isEliminated[currentIndex]; pass++) {
11 // Mark the current player as eliminated.
12 isEliminated[currentIndex] = true;
13
14 // Calculate the next player to be eliminated, wrapping around if necessary.
15 currentIndex = (currentIndex + pass * skipCount) % numPlayers;
16 }
17
18 // Collect the indexes of the players who were not eliminated.
19 for (let index = 0; index < isEliminated.length; index++) {
20 if (!isEliminated[index]) {
21 // Players are numbered starting from 1, hence adding 1 to the index.
22 losers.push(index + 1);
23 }
24 }
25
26 // Return the list of losers.
27 return losers;
28}
29
Time and Space Complexity
Time Complexity
The given code generates a sequence of numbers to simulate a circular game where players are eliminated in rounds based on the number k
. The while loop runs until it finds a previously visited index, which signifies the end of one complete cycle.
The worst-case time complexity occurs when the loop runs for all n
players before any player is visited twice, so it will run for n
iterations. Inside the loop, the main operations are:
- Setting the
vis[i]
value toTrue
, - Calculating the next index
i
using arithmetic operations, and - Incrementing the value
p
.
The above operations are O(1) for each iteration.
Therefore, considering all the operations inside the loop, the worst-case time complexity of this code is O(n)
.
Space Complexity
The space complexity is determined by the amount of memory used in relation to the input size. The space used in the code comes from:
- The
vis
list, which is initialized to the size of the number of playersn
. This list takes upO(n)
space. - Variables
i
, andp
, which take constant space, thusO(1)
.
There are no additional data structures that grow with the input size, thus the overall space complexity of the code is O(n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
Which of the following is a good use case for backtracking?
Recommended Readings
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
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!