🔗 🟡 1823. Find the Winner of the Circular Game 1412

tags: Weekly Contest 236 陣列(Array) 模擬(Simulation) 佇列(Queue) 遞迴(Recursion) 數學(Math)

題意

nn 個人正在玩一個遊戲。所有人坐成一圈,按照 順時針順序11nn 編號。從第 ii 個人順時針移動一位會到達第 (i+1)(i+1) 個人的位置,其中 1i<n1 \leq i < n,而從第 nn 個人順時針移動一位會回到第 11 個人的位置。

遊戲規則如下:

  1. 從第 11 個人所在位置開始。
  2. 沿著順時針方向數 kk 個人,計數時需要 包含 起始時的那位,被數到的人會離開圓圈並視為輸掉遊戲。
  3. 如果圓圈中仍然有不止一個人,從剛剛輸掉的人的 順時針下一位 開始,回到步驟 22 繼續執行。
  4. 否則,圓圈中最後一個人獲勝。

給定參與遊戲的人數 nn 和整數 kk,請返回遊戲的獲勝者編號。

思路

方法一:模擬(Simulation) + 佇列(Queue)

根據題意,最直觀的方式就是使用 佇列(Queue)模擬(Simulation) 遊戲過程。在每一輪遊戲中,我們從佇列的前端取出 k1k-1 個人,並將他們依次加到佇列的尾部,最後將 第 kk 個人,也就是佇列前端的人移出。重複這個過程直到佇列中只剩下一個人,即為獲勝者。

複雜度分析

  • 時間複雜度:O(nk)\mathcal{O}(n \cdot k),每一輪遊戲需要移動 k1k-1 個人,並移除 11 個人,總共需要進行 n1n-1 輪。
  • 空間複雜度:O(n)\mathcal{O}(n),使用了一個佇列來存儲所有參與遊戲的人。
1
2
3
4
5
6
7
8
class Solution:
def findTheWinner(self, n: int, k: int) -> int:
q = deque(range(1, n+1))
while len(q) > 1:
for _ in range(k-1):
q.append(q.popleft())
q.popleft()
return q[0]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int findTheWinner(int n, int k) {
queue<int> q;
for (int i = 1; i <= n; i++) q.push(i);
while (q.size() > 1) {
for (int i = 0; i < k-1; i++) {
q.push(q.front());
q.pop();
}
q.pop();
}
return q.front();
}
};

方法二:約瑟夫問題(Josephus Problem) + 遞迴(Recursion)

事實上,這個問題是經典的數學問題,稱為 約瑟夫問題(Josephus Problem) ,可以使用遞迴的方式來解決這個問題。

J(n,k)J(n, k) 表示有 nn 個人參與遊戲,每次數 kk 個人,最後獲勝者的位置,此處先以 0based0-based 來表示位置。根據遞迴的思路,我們可以通過以下公式來計算 J(n,k)J(n, k)

  • n=1n = 1,則獲勝者的位置為 00
  • n>1n > 1,則獲勝者的位置可以通過上一次遊戲的獲勝者位置和 kk 的關係推算出來:J(n,k)=(J(n1,k)+k)modnJ(n, k) = (J(n-1, k) + k) \bmod n。具體推導過程可以參考參考資料中的連結。

由於我們的數字是從 11nn 而不是從 00 開始,因此需要在取模之前先減去 11,最後再加上 11。修改後的公式如下:

  • J(1,k)=1J(1, k) = 1
  • J(n,k)=((J(n1,k)+k1)modn)+1J(n, k) = ((J(n-1, k) + k - 1) \bmod n) + 1

根據 J(n,k)J(n, k) 的公式,使用遞迴的方式即可求解出最終的答案。

複雜度分析

  • 時間複雜度:O(n)\mathcal{O}(n),需要遞迴 nn 次來求解最終的答案。
  • 空間複雜度:O(n)\mathcal{O}(n),遞迴過程中使用的堆疊空間。
1
2
3
class Solution:
def findTheWinner(self, n: int, k: int) -> int:
return 1 if n == 1 else (self.findTheWinner(n-1, k) + k - 1) % n + 1
1
2
3
4
5
6
class Solution {
public:
int findTheWinner(int n, int k) {
return (n == 1) ? 1 : (findTheWinner(n-1, k) + k - 1) % n + 1;
}
};

方法三:約瑟夫問題(Josephus Problem) + 迭代(Iteration)

同樣地,根據 J(n,k)J(n, k) 的公式,我們可以使用 迭代(Iteration) 的方式來求解最終的答案。

初始化答案 ans=1ans = 1,從 i=2i = 2 開始遍歷 [2,n][2, n],每次更新答案 ans=(ans+k1)modi+1ans = (ans + k - 1) \bmod i + 1

最後返回 ansans 即可。

複雜度分析

  • 時間複雜度:O(n)\mathcal{O}(n),需要遍歷 n1n-1 次來求解最終的答案。
  • 空間複雜度:O(1)\mathcal{O}(1)
1
2
3
4
5
6
class Solution:
def findTheWinner(self, n: int, k: int) -> int:
ans = 1
for i in range(2, n+1):
ans = (ans + k - 1) % i + 1
return ans
1
2
3
4
5
6
7
8
9
10
class Solution {
public:
int findTheWinner(int n, int k) {
int ans = 1;
for (int i = 2; i <= n; i++) {
ans = (ans + k - 1) % i + 1;
}
return ans;
}
};

參考資料


寫在最後

Cover photo is remixed from @吃肥皂泡泡, thanks for their work!