每日一題 - 劍指 Offer 62. 圓圈中最後剩下的數字
題目資訊
-
時間: 2019-07-06
-
題目連結:Leetcode
-
tag: 動態規劃 迭代 約瑟夫環
-
難易程度:中等
-
題目描述:
0,1,,n-1這n個數字排成一個圓圈,從數字0開始,每次從這個圓圈裡刪除第m個數字。求出這個圓圈裡剩下的最後一個數字。
例如,0、1、2、3、4這5個數字組成一個圓圈,從數字0開始每次刪除第3個數字,則刪除的前4個數字依次是2、0、4、1,因此最後剩下的數字是3。
示例1:
輸入: n = 5, m = 3
輸出: 3
示例2:
輸入: n = 10, m = 17
輸出: 2
注意
1. 1 <= n <= 10^5 2. 1 <= m <= 10^6
解題思路
本題難點
約瑟夫環
N個人圍成一圈,第一個人從1開始報數,報M的將被殺掉,下一個人接著從1開始報。如此反覆,最後剩下一個,求最後的勝利者。
具體思路
約塞夫問題就是用人來舉例的,那我們也給每個人一個編號(索引值),每個人用字母代替
下面這個例子是N=8 m=3
的例子
我們定義F(n,m)
表示最後剩下那個人的索引號
,因此我們只關係最後剩下來這個人的索引號的變化情況即可
從8個人開始,每次殺掉一個人,去掉被殺的人,然後把殺掉那個人之後的第一個人作為開頭重新編號
- 第一次C被殺掉,人數變成7,D作為開頭,(最終活下來的G的編號從6變成3)
- 第二次F被殺掉,人數變成6,G作為開頭,(最終活下來的G的編號從3變成0)
- 第三次A被殺掉,人數變成5,B作為開頭,(最終活下來的G的編號從0變成3)
- 以此類推,當只剩一個人時,他的編號必定為0!(重點!)
現在我們知道了G的索引號的變化過程,那麼我們反推一下
從N = 7
到N = 8
的過程
如何才能將N = 7
的排列變回到N = 8
呢?
我們先把被殺掉的C補充回來,然後右移m個人,發現溢位了,再把溢位的補充在最前面
因此我們可以推出遞推公式f(8,3)=[f(7,3)+3]%8
進行推廣泛化,即f(n,m)=[f(n−1,m)+m]%n
- 遞推公式
提示 : 最終剩下的數字的陣列下標為0;
程式碼
class Solution { public int lastRemaining(int n, int m) { int res = 0 ; for(int i = 2 ; i <= n; i++){ res = (res + m) % i; } return res; } }
複雜度分析:
- 時間複雜度 O(N) :其中 N為 迭代n次的長度。
- 空間複雜度 O(1) : 變數 res 使用常數大小的額外空間。
其他優秀解答
解題思路
模擬連結串列。純暴力的做法,每次找到刪除的那個數字,需要 O(m) 的時間複雜度,然後刪除了 n−1 次。但實際上我們可以直接找到下一個要刪除的位置的。假設當前刪除的位置是 idx,下一個刪除的數字的位置是 idx+m 。但是,由於把當前位置的數字刪除了,後面的數字會前移一位,所以實際的下一個位置是 idx+m−1。由於數到末尾會從頭繼續數,所以最後取模一下,就是 (idx+m−1)(modn)。
程式碼
class Solution {
public int lastRemaining(int n, int m) {
ArrayList<Integer> list = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
list.add(i);
}
int idx = 0;
while (n > 1) {
idx = (idx + m - 1) % n;
list.remove(idx);
n--;
}
return list.get(0);
}
}