【 剑指offer 】JZ46 child 们的游戏(圆圈中最后剩下的数)【垃圾CSDN,连child的中文名都不让说】

前言:

这是一个约瑟夫环的问题,主要解决思想有两个,第一个是模拟过程,第二个是经典的解法。

参考文档:约瑟夫环问题

题目:

题目如下:
在这里插入图片描述

1. 傻瓜解法

傻瓜解法就是模拟约瑟夫环的过程,因为约瑟夫环涉及到删除元素,故使用vector来实现。

代码:

class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        if(n==0)return -1;
        vector<int> tmp(n);
        for(int i=0;i<n;i++){
            tmp[i]=i;
        }
        int size=tmp.size();
        int index=0;
        while(size>1){
            index=(index+m-1)%size;//使用index模拟每次删除掉的下标,注意这里是 m-1
            auto it=tmp.begin();
            std::advance(it,index);
            tmp.erase(it);//在删除掉index位置的元素后,下一个元素左移,即index此时指向了下一个元素
            size--;
        }
        return tmp[0];
    }
};

如上代码所示,是模拟约瑟夫环的过程,使用一个vector数组,数组中的值初始化为其对应的下标,即编号。其中可能比较重要的是使用模板库函数来移动迭代器,或者说用vector的函数来删除指定元素。其他的没啥了就。

当然还有一种方法,就是用for循环,真实的模拟没次迭代过程,就是指针真的是在环中用 i++ 来跑,而不是用取余来操作模拟。

2. 经典解法

经典解法参考了上面列出的参考文档,这种解法不用考虑环的断路问题,每次迭代,环都是连续的,故可以一直使用取余操作来决定此次迭代应该选择那个人,维持一个差值数组,用来记录每次迭代与其上一次迭代之间的差值,(注,这里的差值仅仅记录的是此次迭代与上次迭代之间的差值,仅仅记录他们俩之间的关系),使用差值可以方便的将此次迭代的数值映射到上次迭代的位置,如此,当约瑟夫环仅仅只剩下一个人的时候,可以根据差值一直回退到最初的状态。

代码:

class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        if(n==0)return -1;
        vector<int> tmp;
        int k=n;
        while(k>1){
            int p=(m-1)%k;
            if(k>m)tmp.push_back(m);
            else tmp.push_back(m-k);
            k--;
        }
        int res=0;
        k=2;
        for(int i=tmp.size()-1;i>=0;i--){
            res=(res+tmp[i])%(k++);
        }
        return res;
    }
};

下面这个是最好想的结果:就是用变量K记录每个选到的孩子,那么无论当前人数为多少,因为我的n限定了必须大于1,所以必然会存在至少两个孩子,故不用考虑人数不够的情况。

那么既然人数大于等于2,那么就可以考虑我的K的落点,分为两种情况,

  1. K落在了 0 ~ n-2,这样的话,K+1后仍然没有超出 0 ~ n-1 的范围,所以K的下一个值就是偏移值
  2. K落在了 n-1,这样的话,下一轮次的起始还是0,偏移量为0,所以应该是 K+1 % n 。来防止落到边界情况。
class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        vector<int> tmp;
        while(n>1){
            int k = m-1 % n;
            tmp.push_back((k+1)%n);
            n--;
        }
        int res = 0;
        int l = 2;
        for(int i=tmp.size()-1;i>=0;i--){
            res += tmp[i];
            res %= l++; 
        }
        return res;
    }
};

可以看到代码很简单,下面说一下具体的过程,就明白了:

0 1 2 3 4
0 1   3 4				//选中2号,将2号剔除,从3号开始归 0

2 3   0 1 tmp=3			//上次迭代的3号开始从0数,因为3号从3变为了0号,故维持差值为 tmp=3
  3   0 1				//选中2号,将2号剔除,从3号开始归 0
 
  0   1 2 tmp=3			//上次迭代的3号开始从0数,因为3号变成了0号,故维持差值为 tmp=3
  0   1  				//选中2号,将2号剔除,因为此时2号就是最后一个人,所以不做任何处理
  			//也就是当剩余人数K等于报数M的时候,数到M-1的人必然位于最后一个,这时整个序列不变
    
  0   1   tmp=0			//序列保持不变,tmp = 0
      1   				//由取余运算符得知,下次应该选中0号,剔除0号,从1号开始归 0

      0   tmp=1			//从上次迭代的1号开始从0数,此时人数K已经缩到了1人,则此人即为最后一人
    
回退式子如下:
TMP=0;
1. 由总人数1人回退到2-> TMP = (0 + tmp) % 2
2. 由总人数2人回退到3-> TMP = (TMP + tmp) % 3
3. 由总人数3人回退到4-> TMP = (TMP + tmp) % 4
4. 由总人数4人回退到5-> TMP = (TMP + tmp) % 5
return TMP;  
return ((((0 + tmp)%2 + tmp)%3 + tmp)%4 +tmp) % 5;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值