圆圈中最后剩下的数字

题目 圆圈中最后剩下的数字

image-bc268ee5

思路分析

著名的约瑟夫环问题

可以直接用环形列表来模拟,每次从这个列表中删除第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)时间删除链表结点