约瑟夫环

题目:有0,1,...,n-1这n个数字排成一个圈,从数字0开始每次从这个圈中删除第m个数字,求出这个圆圈里剩下的最后一个数字。

方法一:用环形链表模拟圆圈

int lastRemaining(unsigned int n,unsigned int m){
    if(n < 1 || m < 1)
        return -1;
    unsigned int i = 0;
    list<int> ilist;
    for(;i < n;i++)
        ilist.push_back(i);
    list<int>::iterator iter = ilist.begin();
    while(ilist.size > 1){
        for(i = 1; i < m;i++){
            iter++;
            if(iter == ilist.end())
                iter = ilist.begin();
        }
        list<int>::iterator iterNext = ++iter;
        if(iterNext = ilist.end())
            iterNext = ilist.begin();
        iter--;
        ilist.erase(iter);
        iter = iterNext;
    }
    return (*iter);
}
时间空间复杂度分析:每删除一个数字需要m步运算,总共有n个数字,故总的时间复杂度为O(mn),同时还需要一个辅助链表来模拟圆圈,其空间复杂度为O(n)。

方法二:通过数学方法找出要删除数字的规律

我们定义一个函数f(n,m,i),它的意义是对于一个有n个元素的环,每次删除第m个元素,删除i次之后最后剩下的元素为f(n,m,i)。由此我们知道f(n,m,i) = f(n-1,m,i-1);

①设最初的环由0 1 2 ... n-1组成,删除的第m个元素为下标为(m-1)%n的元素,我们设为k,即k=(m-1)%n;

②去除第m个元素后剩下的元素为0 1 2 ... k-1 k+1... n-1,接下来我们以第k+1个元素为起点,再找第m个元素,也即k+1 k+2 ... n-1 0 1... k-1

如果我们对其进行一下变换

k+1 -> 0

k+2 -> 1

...

n-1 -> n-k-2

0 -> n-k-1

....

k-1 -> n-2

也就是说左边的x经过f(x) = (x-k-1)%n得到右边的数(这里其实是下标,但是因为数和下标相同,所以也可以认为是数)。  其逆映射为p(x)=(x+k+1)%n。

③由于映射后的序列和最初的序列具有同样的形式,即都是从0开始的连续序列,因此仍然可以用函数f表示,记为f(n-1,m),根据我们的映射规则,映射之前的序列中最后剩下

的数字f ' (n-1,m) = p[f(n-1,m)] = [f(n-1,m)+k+1]%n,把k=(m-1)%n带入得到f(n,m) = [f(n-1,m)+m]%n。

④由此我们找到了一个递归公式,要得到n个数字的序列中最后剩下的数字,只需要得到n-1个数字的序列中最后剩下的数字,并依此类推。当n=1时,也就是序列中开始只有一

个数字0,那么很显然最后剩下的就是0。

循环实现代码:

int lastRemaining(unsigned int n,unsigned int m){
    if(n < 1 || m < 1)
        return -1;
    int last = 0;
    for(int i = 2; i < n; i++){
        last = (last + m) % i;
    }
    return last;
}

方法同上类似,只不过是用递归实现:

假设n=10,m=3,那么0 1 2 3 4 5 6 7 8 9在第一个数字出列后为0 1 3 4 5 6 7 8 9,即3 4 5 6 7 8 9 0 1(*),我们要将其转化为0 1 2 3 4 5 6 7 8 (**)

我们发现(**)通过((**)+3)/10可以转化为(*),也就是我们求出9个人中第9次出环的编号,最后经过上面的转化就可以得到10个人经过10次出环的编号了。

设f(n,m,i)为n个人的环,报数为m,第i个人出环,则f(10,3,10)是我们需要的结果,

当i = 1时,f(n,m,i) = (n+k-1)%n;

当i != 1时,f(n,m,i) = (f(n-1,m,i-1)+m)%n;

int fun(int m,int k,int i){
 
    if(i==1)
        return (m+k-1)%m;
    else
        return (fun(m-1,k,i-1)+k)%m;
 
}
int main(int argc, char* argv[])
{
     
    for(int i=1;i<=10;i++)
        printf("第%2d次出环:%2d\n",i,fun(10,3,i));
    return 0;
}












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值