前言:
这是一个约瑟夫环的问题,主要解决思想有两个,第一个是模拟过程,第二个是经典的解法。
参考文档:约瑟夫环问题
题目:
题目如下:
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的落点,分为两种情况,
- K落在了 0 ~ n-2,这样的话,K+1后仍然没有超出 0 ~ n-1 的范围,所以K的下一个值就是偏移值
- 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;