圆圈中最后剩下的数字
题目 圆圈中最后剩下的数字
思路分析
著名的约瑟夫环问题
可以直接用环形列表来模拟,每次从这个列表中删除第m个元素,一直到列表最后剩下一个元素为止
最简单的方式是直接用list来模拟这个环形列表,由于list并不是一个环形的结构,因此每次跌代器扫描到列表末尾的时候,要记得把迭代器移到列表的头部。这样就是按照一个圆圈的顺序来遍历这个列表了。
然后可以用数组模拟的链表尝试实现(TMD别找罪受 越界问题调了一晚上 草 能用stl快速解决 且不会卡时间 何必呢 唉)
再就是用简单DP 也就是递推
首先定义最初的n个数字(0,1,…,n-1)中最后剩下的数字是关于n和m的方程为f(n,m)。
在这n个数字中,第一个被删除的数字是(m-1)%n,为简单起见记为k。那么删除k之后的剩下n-1的数字为0,1,…,k-1,k+1,…,n-1,并且下一个开始计数的数字是k+1。相当于在剩下的序列中,k+1排到最前面,从而形成序列k+1,…,n-1,0,…k-1。该序列最后剩下的数字也应该是关于n和m的函数。由于这个序列的规律和前面最初的序列不一样(最初的序列是从0开始的连续序列),因此该函数不同于前面函数,记为f’(n-1,m)。最初序列最后剩下的数字f(n,m)一定是剩下序列的最后剩下数字f’(n-1,m),所以f(n,m)=f’(n-1,m)。
接下来我们把剩下的的这n-1个数字的序列k+1,…,n-1,0,…k-1作一个映射,映射的结果是形成一个从0到n-2的序列:
k+1 -> 0
k+2 -> 1
…
n-1 -> n-k-2
0 -> n-k-1
…
k-1 -> n-2
把映射定义为p,则p(x)= (x-k-1)%n,即如果映射前的数字是x,则映射后的数字是(x-k-1)%n。对应的逆映射是p-1(x)=(x+k+1)%n。
由于映射之后的序列和最初的序列有同样的形式,都是从0开始的连续序列,因此仍然可以用函数f来表示,记为f(n-1,m)。根据我们的映射规则,映射之前的序列最后剩下的数字f’(n-1,m)= p-1 [f(n-1,m)]=[f(n-1,m)+k+1]%n。把k=(m-1)%n代入得到f(n,m)=f’(n-1,m)=[f(n-1,m)+m]%n。
经过上面复杂的分析,我们终于找到一个递归的公式。要得到n个数字的序列的最后剩下的数字,只需要得到n-1个数字的序列的最后剩下的数字,并可以依此类推。当n=1时,也就是序列中开始只有一个数字0,那么很显然最后剩下的数字就是0。因此有递推公式:
当n=1时,f(n, m) = 0 当n>1时,f(n, m) = [f(n-1, m) +m] % n
具体看y总视频
代码实现
list
#include<bits/stdc++.h>
class Solution {
public:
int lastRemaining(int n, int m){
list<int> nums;
for(int i=0;i<n;i++){
nums.push_back(i);
}
auto it=nums.begin();
int k=m-1;
while(nums.size()>1){
while(k--){
it++;
if(it==nums.end())
it=nums.begin();
}
it=nums.erase(it);
if(it==nums.end())
it=nums.begin();
k=m-1;
}
return nums.front();
}
};
递归
class Solution {
public:
int lastRemaining(int n, int m){
if(n==1)
return 0;
else
return (lastRemaining(n-1,m)+m)%n;
}
};
同类题型
视频讲解
⬅️ 围圈报数 🏠 00-刷题理模型 ➡️ 在O(1)时间删除链表结点
💬 评论