--- title: "圆圈中最后剩下的数字" created: 2025-11-28 tags: - 算法 --- # 圆圈中最后剩下的数字 ## 题目 [圆圈中最后剩下的数字](https://www.acwing.com/problem/content/description/78/) ![[image-bc268ee5.png]] ## 思路分析 著名的约瑟夫环问题 可以直接用环形列表来模拟,每次从这个列表中删除第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** ```java #include class Solution { public: int lastRemaining(int n, int m){ list nums; for(int i=0;i1){ 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(); } }; ``` **递归** ```java 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)时间删除链表结点|在O(1)时间删除链表结点]]