(可提交代码)原题链接: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;
}