描述
这是一道典型的约瑟夫环问题,我们可以这样理解:有n个人围成一圈,从第一个人开始报数,第m个将被杀掉,最后剩下一个,其余人都被杀掉,返回最后剩下的这个人的索引,如果没有人,则返回-1
示例

输入:
5,3
返回值:
3

方法一:环形链表
我们可以用环形链表来存储数字,每次找到被选中的元素将其移除, 删除元素的时间复杂度为,但是查找元素的时间复杂度贼为,相比于,时间复杂度会降低

  • 创建环形链表存储元素

图片说明

  • 因为链表是环形的,所以被删除元素的索引应该对n取模,每次定位到被删除元素处直接进行remove操作,同时n需要减1

图片说明

import java.util.*;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n<=0)return -1;
        List<Integer>list=new ArrayList<Integer>();
        for(int i=0;i<n;i++){
            list.add(i);
        }
        int index=0;
        while(n>1){
            index=(index+m-1)%n;
            list.remove(index);
            n--;
        }
      return list.get(0);
    }
}

复杂度

  • 时间复杂度:由于数组移除元素的时间复杂度为循环次,所以时间复杂度为
  • 空间复杂度:需要额外的辅助空间

方法二:迭代
我们知道第一个人(编号一定是) 出列之后,剩下的个人组成了一个新的约瑟夫环(以编号为= 的人开始):
并且从开始报0。
我们把他们的编号做一下转换:




...
...


变换后就成为个人报数的子问题,假如我们知道这个子问题的解:例如是最终的胜利者,那么根据上面这个表把这个变回去刚好就是个人情况的解。可以推出来:
如何知道个人报数的问题的解,只要知道个人的解就行了,个人的解呢,就是先求的情况 ---- 这显然就是一个倒推问题。思路出来了,下面写递推公式:
表示个人玩游戏报退出最后胜利者的编号,最后的结果是
递推公式



有了这个公式,我们要做的就是从顺序算出的数值,最后结果是。这里我们从1开始,输出
由于是逐级递推,不需要保存每个
代码如下

import java.util.*;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n<=0)return -1;
        int index=0;
        for(int j=2;j<=n;j++){
            index=(index+m)%j;
        }
        return index;
    }
}

复杂度
• 时间复杂度:只有一个循环,

• 空间复杂度:没有利用额外的辅助空间,
方法三:递归

class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        if(n==0)return 0;
        else{
           return (LastRemaining_Solution(n-1,m)+m)%n;}
    }
};

复杂度
• 时间复杂度:递归算法的时间复杂度=递归次数*每次递归的操作次数,递归次数为,每次操作的时间复杂度为常数级,所以时间复杂度为

• 空间复杂度:递归运行时栈的大小不超过