圆圈中最后剩下的数字

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

思路

使用链表模拟。

代码

#include <list>
class Solution {
public:
    int lastRemaining(int n, int m) {
        std::list<int> lister;
        for(int i = 0;i < n;i++) {
            lister.push_back(i);
        }
        auto cur = lister.begin();
        while(lister.size() > 1) {
            for(int i = 1;i < m;i++) {
                cur++;
                if(cur == lister.end()) {
                    cur = lister.begin();
                }
            }
            auto tmp = cur;
            cur++;
            if(cur == lister.end()) {
                cur = lister.begin();
            }
            lister.erase(tmp);
        }
        return lister.back();
    }
};

思路二

数学.记f(n,m)表示最后剩下的数字,则删除一个数后,记f(n-1,m)表示最后剩下的数字,显然有f(n,m)=f'(n-1,m)。假设被删除的数下标为k,则k=(m-1)%n,对f'(n-1,m)序列作处理,每个数应用p(x)=(x-k-1)%n,则得到0,1,...,n-2序列,即f(n-1,m).而p(x)=(x-k-1)%n的逆操作为p-1(x)=(x+k+1)%n。所以有下面的推导式:
f(n,m)=f'(n-1,m)=[f(n-1,m)+k+1)]%n=[f(n-1,m)+(m-1)%n+1]%n=[f(n-1,m)+m]%n,省略中间式,最终结果为:
f(n,m)=[f(n-1,m)+m]%n,而这就是动态规划转移方程。使用动态规划,显然n=1,结果为0。推导过程从2到n。f(n,m)只和f(n-1,m)有关,使用一个变量表示即可。可以表示为 last = (last+m)%n。

代码

class Solution {
public:
    int lastRemaining(int n, int m) {
        int last = 0;
        for(int i = 2;i <= n;i++) {
            last = (last+m)%i;
        }
        return last;
    }
};