1823. Find the Winner of the Circular Game
Problem Description
In this game, n
friends are sitting in a circle numbered from 1
to n
. The game follows a set of rules where the friends count off from 1
to k
in a clockwise direction, and each time the count reaches k
, that person is removed from the circle. The counting process starts from the next person in the circle after the one who just got removed, and it continues until there's only one person left, who is declared the winner. The problem is to find out who will be the winner.
Intuition
The solution approach uses recursion to simulate the elimination process.
-
Base Case - If there is only one person (
n == 1
), they are the winner because there's nobody else to play the game with. -
Recursive Case - When there are
n
people in the game, we can first find out who will be the winner among thesen
people without considering the counting and elimination process. To do this, we recursively solve the problem forn-1
people to get the winner. -
Since the game is circular, we need to handle the counting wrap around. This means if we reach the end of the circle while counting, we start from the beginning again. We accommodate this by using modulus operation (
% n
), which effectively resets the count when we reachn
(the end of the circle). -
After the recursive call, we adjust the position because we need to account for the shifted positions after each elimination. When the person at position
k
is eliminated, the next counting should start fromk+1
, but due to the removal, positions of everyone are shifted one place to the left. This gives the equationans = (k + self.findTheWinner(n - 1, k)) % n
. -
The modulus might return
0
, but since the friends are numbered starting from1
, ifans == 0
, we should returnn
(because friendn
would be in that position after the wrap around).
This recursive pattern continues until we reach the base case and then we unwind the recursion stack back to the original problem size (n
), at each step applying the position adjustment. Eventually, we can find the winner of the game.
Solution Approach
The implementation uses a straightforward recursive function to simulate the elimination process in the game.
Here's a more detailed walkthrough of the code:
-
The function
findTheWinner
takes two arguments:n
(number of friends) andk
(the count at which friends are eliminated). -
It first checks if
n
is equal to1
, which is our base case. If this is true, it returns1
since the last remaining friend is the winner by default. -
The heart of the function is the recursive call. The function calls itself, but with
n - 1
instead ofn
. This simulates the game with one less friend, as one friend is supposed to get eliminated in every round. -
The recursive call will eventually hit the base case and then return, unwinding the recursion. Every recursive step is calculating who would be the winner if the game started with one less player.
The key here is to understand what
(k + self.findTheWinner(n - 1, k)) % n
is doing:self.findTheWinner(n - 1, k)
: This gives us the winner with one less player.k + self.findTheWinner(n - 1, k)
: This assumes that the elimination game starts right after the last winner. We addk
because our counting includes the person we start with.(k + self.findTheWinner(n - 1, k)) % n
: We use the modular operator to wrap the counting around the circle since the circle is closed and we need to determine the actual position in the current round.
-
The variable
ans
holds the result of the modular arithmetic. Ifans
equals0
, it means the position has wrapped around to the end of the circle, and hence the winner should be the nth friend. Otherwise,ans
is already the correct position for the winner. -
Finally, the function returns
ans
.
The algorithm does not use any additional data structures and simply relies on the call stack to perform the recursive calls. It follows the pattern of solving a smaller instance of the problem (with one less friend) and using that solution to build up to the full problem's solution.
This approach is efficient because it simplifies a seemingly complex circular problem into a series of linear steps using the properties of modular arithmetic to keep track of positions.
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 take a small example where n = 5
friends are sitting in a circle and k = 2
, which means every second person is eliminated. We will illustrate the solution approach using this example:
-
Imagine 5 friends sitting in a circle, numbered from 1 to 5. We start counting from friend 1 to friend 5 repeatedly.
-
In the first round, we start counting from friend 1. The counting goes
1 -> 2
. Since we are removing every second friend, friend 2 is eliminated. -
Now we have friends 1, 3, 4, and 5 left. The next round starts from friend 3, and we count
3 -> 4
, so now friend 4 gets eliminated. -
The circle is now down to friends 1, 3, and 5. The next round starts from friend 5 and the counting goes
5 -> 1
. This eliminates friend 1. -
With friends 3 and 5 left, we start from friend 3 and count two places, arriving back at friend 3. Friend 3 is now eliminated.
-
We are left with friend 5, who is the winner of this game according to our rules.
Now let's walk through how our recursive algorithm would find this winner:
-
We call
findTheWinner(5, 2)
. Sincen
is not 1, the base case does not apply, and we proceed to the recursive case. -
We make a recursive call
findTheWinner(5 - 1, 2)
, which is the same asfindTheWinner(4, 2)
. Think of this as "simulating" the game with one less friend (since friend 2 got eliminated). -
Continuing recursively, we would call
findTheWinner(3, 2)
,findTheWinner(2, 2)
and finallyfindTheWinner(1, 2)
, which is our base case. HerefindTheWinner(1, 2)
would return 1 because only one friend is left. -
Now, we unwind the recursion. When we called
findTheWinner(2, 2)
, based on our equation the result would be(2 + 1) % 2
which equals 1. So, if the game had started with two people and we were eliminating every second person, friend 1 would win. -
Back at
findTheWinner(3, 2)
, the result would be(2 + 1) % 3
which equals 0. Since numbering starts at 1, we consider the last position, which is friend 3, as the winner when there are 3 participants. -
Continuing this process, when we reach back to our original problem
findTheWinner(5, 2)
, we apply our equation(2 + winnerFromPreviousStep) % 5
. Through our recursion, the combined rolls yield the same pattern that emerged when we manually simulated the game: friend 5 is the winner. -
The function returns the value
5
, correctly identifying friend 5 as the winner of the game.
Through the recursive calls, the solution approach effectively tracks back from the base case to the original full circle, at each stage considering the winner if the circle had one less friend, and adjusting for elimination and wraparound using modular arithmetic.
Solution Implementation
1class Solution:
2 def findTheWinner(self, n: int, k: int) -> int:
3 # Base case: If there is only one person, they are the winner.
4 if n == 1:
5 return 1
6
7 # Recursive call to find the winner among (n-1) people.
8 # The winner's position among n people is offset by k positions from
9 # the position among (n-1) people.
10 winner = (k + self.findTheWinner(n - 1, k)) % n
11
12 # If modulo operation results in 0, the winner is at the nth position
13 # because indexing starts from 1, not 0.
14 # Otherwise, return the calculated winner position.
15 return n if winner == 0 else winner
16
1class Solution {
2 // This method determines the winner in a game where n people
3 // are sitting in a circle and elimination occurs at every kth person
4 public int findTheWinner(int n, int k) {
5 // Base case: if there is only one person, they are the winner
6 if (n == 1) {
7 return 1;
8 }
9 // Recursively find the winner for n - 1 people
10 // and adjust the position with the offset k
11 int winner = (findTheWinner(n - 1, k) + k) % n;
12
13 // Java uses 0-based indexing but the problem assumes 1-based indexing.
14 // Therefore, if the result is 0, we convert it to n (the last person),
15 // as the modulo operation may result in zero.
16 // Otherwise, we return the calculated position.
17 return winner == 0 ? n : winner;
18 }
19}
20
1class Solution {
2public:
3 // This function determines the winner in a game simulating the Josephus problem.
4 // 'n' represents the number of players, and 'k' is the count after which a player is eliminated.
5 int findTheWinner(int n, int k) {
6 if (n == 1)
7 return 1; // Base case: if there's only one player, they're the winner.
8
9 // Recursive call to find out the winner among n-1 players.
10 int winner = findTheWinner(n - 1, k);
11
12 // Adjusting the winner index for the current round, considering 0-based indexing.
13 int adjustedWinner = (winner + k) % n;
14
15 // If the adjustedWinner is 0, it implies that the winner is the nth player.
16 // Otherwise, return the index considering 1-based indexing.
17 return adjustedWinner == 0 ? n : adjustedWinner;
18 }
19};
20
1// Node definition for a singly linked list node.
2interface ListNode {
3 value: number;
4 next: ListNode | null;
5}
6
7// Function to create a new linked list node.
8function createNode(value: number, next: ListNode | null = null): ListNode {
9 return { value, next };
10}
11
12// Function to find the winner in the game.
13function findTheWinner(n: number, k: number): number {
14 // If k equals 1, the winner is the last person, because no one is eliminated.
15 if (k === 1) {
16 return n;
17 }
18
19 // Create a dummy node to help with circular list operations.
20 let dummy: ListNode = createNode(0);
21 let current = dummy;
22
23 // Construct the circular linked list.
24 for (let i = 1; i <= n; i++) {
25 current.next = createNode(i);
26 current = current.next;
27 }
28 // Complete the circular list by linking the last node back to the first node.
29 current.next = dummy.next;
30
31 // Start with the dummy node, which is right before the first node.
32 current = dummy;
33 let count = 0;
34
35 // Loop until a single node is left in the circular linked list.
36 // This node will point to itself.
37 while (current.next !== current) {
38 // Walk the list and increment the count.
39 count++;
40 // If the count reaches k, remove the kth node.
41 if (count === k) {
42 current.next = current.next.next;
43 count = 0; // Reset count after elimination.
44 } else {
45 // Move to the next node.
46 current = current.next;
47 }
48 }
49
50 // Return the value of the surviving node.
51 return current.value;
52}
53
Time and Space Complexity
The given Python code is a recursive implementation for finding a winner in a game where n
people are sitting in a circle, and we eliminate every k-th
person until only one person is left.
Time Complexity:
The time complexity of this recursive solution is O(n)
. This is because the findTheWinner function is called recursively n-1
times until n
reaches 1
. In each recursive call, the computation performed is in constant time O(1)
, so the total time complexity is a sum of n-1
constant-time operations, which simplifies to O(n)
.
Space Complexity:
The space complexity of this recursive solution is also O(n)
. This is because the maximum depth of the recursion call stack will be n-1
. Each call uses a constant amount of space, but since calls are not returned until the base case (n == 1) is met, it requires space proportional to the number of recursive calls, which is n-1
. Thus, the space complexity simplifies to O(n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
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
Queue Intro Think of the last time you stood in line to buy movie tickets The first person in line gets their ticket first and any newcomers join the end of the line This system where the first person to arrive is the first to be served is a queue in real
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Want a Structured Path to Master System Design Too? Don’t Miss This!