(可提交代碼)原題鏈接:https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/
題目:
0,1,...,n-1這n個數字排成一個圓圈,從數字0開始,每次從這個圓圈裏刪除第m個數字。求出這個圓圈裏剩下的最後一個數字。
例如,0、1、2、3、4這5個數字組成一個圓圈,從數字0開始每次刪除第3個數字,則刪除的前4個數字依次是2、0、4、1,因此最後剩下的數字是3。
解題思路一:模擬題目中的過程
- 寫出題目中的每一步過程,(以題目中例子爲主,n=5,m=3)
- 數到0,是第1個數字,數下一個。此時的數字順序變爲:1、2、3、4、0。
- 數到1,是第2個數字,數下一個。此時的數字順序變爲:2、3、4、0、1。
- 數到2,是第3個數字,刪掉。此時的數字順序變爲:3、4、0、1。
- 數到3,是第1個數字,數下一個。此時,刪掉後的數字爲第1個,數字順序變爲:4、0、1、3。
- 數到4,是第2個數字,數下一個。此時的數字順序變爲:0、1、3、4。
- 數到0,是第3個數字,刪掉。此時的數字順序變爲:1、3、4。
- 數到1,是第1個數字。此時,刪掉後的數字爲第1個。此時的數字順序變爲:3、4、1。
- 數到3,是第2個數字,數下一個。此時的數字順序變爲:4、1、3。
- ...
- 總結規律
- 每數到一個數字,若不是第m個,就把他放到數字順序的最後一項;若是第m個,就直接刪掉。
- 選擇合適的數據結構:鏈表或者隊列
- 整理思路,寫代碼。
-
int lastRemaining(int n, int m) { if(n==1) return 0; if(m==1) return n-1; queue<int> que; for(int i=0;i<n;i++) { que.push(i); } int count=1; while(que.size()>1) { if(count==m) { que.pop(); count=1; } else{ int t=que.front(); que.pop(); que.push(t); count++; } } return que.front(); }
存在問題:超時。
-
再次總結規律,減少代碼執行時間
第1次刪除的是數字列表中第(m-1)%n個,設c=(m-1)%n,之後每次刪除的是第(c+m-1)%list.size() 個。
存在問題:依舊超出時間限制。
list與vector區別:https://www.csdn.net/gather_26/NtDakgysMjMtYmxvZwO0O0OO0O0O.html
vector刪除元素用法:https://blog.csdn.net/qq_36770641/article/details/89355657
int lastRemaining(int n, int m) { if(n==1) return 0; if(m==1) return n-1; vector<int> vec; for(int i=0;i<n;i++) { vec.push_back(i); } int c=(m-1)%n; while(vec.size()>1) { //刪除list中第c個元素 vec.erase(vec.begin()+c); //更新c值 c=(c+m-1)%(vec.size()); } return vec[0]; }
解題思路二:使用數學解法
n 個數字的編號如下:0、1、2、3、...、k-1、k、k+1、...、n-1
第一個被剔除的數字是k=(m-1)% n,剔除完結果如下:0、1、2、3、...、k-1、k、k+1、...、n-1
那下一個位置那就是從 k+1 開始啦,我們給它重新排個隊,再編個號:
k+1、k+2、k+3、...、n-1、0、1、2、3、...、k-1
0 1 2 n-2
把映射數字記爲x,原始數字記爲y,那麼映射數字變回原始數字的公式爲 y=(x+k+1) mod n
在映射數字中,n-1個數字,不斷刪除第m個數字,由定義可以知道,最後剩下的數字爲f(n-1,m)。我們把它變回原始數字,由上一個公式可以得到最後剩下的原始數字是(f(n-1,m)+k+1)%n,而這個數字也就是一開始我們標記的f(n,m),所以可以推得遞歸公式爲 f(n,m) =(f(n-1,m)+k+1)mod n
將k=(m-1)%n代入,化簡得到:f(n,m) =(f(n-1,m)+m)\mod n, 且f(1,m) = 0
代碼中可以採用迭代或者遞歸的方法實現該遞歸公式。時間複雜度爲O(n),空間複雜度爲O(1)
注意公式中的mod就等同於%,爲取模運算。值得注意的是,在數學中,下式成立:(a%n+b)%n=(a+b)%n
代碼:
//迭代
int lastRemaining(int n, int m) {
if(n==1) return 0;
if(m==1) return n-1;
//自底向上
int flag=0;
for(int i=2;i<=n;i++)
{
//i表示有i個數的時候
flag=(flag+m)%i;
}
return flag;
}
//遞歸
int lastRemaining(int n, int m) {
if(n==1) return 0;
if(m==1) return n-1;
return (lastRemaining(n-1,m)+m)%n;
}