题目:环形链表的约瑟夫问题

描述:编号为1到n的n个人围成一圈。从编号为1的人开始报数,报到m的人离开。下一个人继续从1开始报数。n−1轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?

示例1:输入:5,2,返回值:3

说明:开始5个人1,2,3,4,5,从1开始报数,1->1,2->2编号为2的人离开

1,3,4,5,从3开始报数,3->1,4->2编号为4的人离开

1,3,5,从5开始报数,5->1,1->2编号为1的人离开

3,5,从3开始报数,3->1,5->2编号为5的人离开

最后留下人的编号是3

解法一:

举例分析+思路分析:约瑟夫环是一个经典的数学问题,如果我们按照规律进行依次报数的话会发现有一个递推公式,下面,我们使用表格进行解释,假设有5个人,第2个人会离开
图片说明
经过第一次,发现数字2离开了,剩余的人数变为4
图片说明
第二次循环,数字4离开了,剩余的人数变为3
图片说明
第三次循环,数字1离开了,剩余的人数变为2
图片说明
最后一次,数字5离开了,剩余的人数变为1
图片说明
最终留下的人的编号为3。

我们得到一个大致的递推公式为:表示N个人中第M个离开,表示N-1个人中第M个离开,根据上表,我们可以得到一个大致的逻辑就是,当有两个人的时候,没有离开的人的序号为0(假设序号是从0开始的),当有三个人的时候,没有离开的人的序号为2,当有四个人的时候,没有离开的人的序号为0……以此类推,便可以通过公式法将最终的结果反推出来,具体公式为,res的初始值为0。由此可以直接编写代码。

C++核心代码为:

class Solution {
public:
    /**
     * 
     * @param n int整型 
     * @param m int整型 
     * @return int整型
     */
    int ysf(int n, int m) {
        // write code here
        int res = 0;
        for(int i = 2;i <= n;i++){
            res = (res + m) % i;//从2 开始进行推导
        }
        return res + 1;
    }
};

该方法直接采用数学公式法进行推导,所以时间复杂度为,没有用到额外的内存空间,空间复杂度为

解法二:

思路分析:我们也可以建立一个单链表对象,首先将所有的值都添加到单链表当中,其次可以不断的判断单链表的大小值,通过设定一个index对象,刚开始赋值为0,只要对单链表大小进行不断取余,然后删除,就可以找到最后一个值的位置。

实例分析:
图片说明
首先令;,根据判断出局第一个元素为2,序号为1。
图片说明
其次出局的元素为4,序号为3。
图片说明
然后出局的元素为1,序号为0。
图片说明
最后出局的元素为5,序号为4。
图片说明
java核心程序为:

import java.util.*;
public class Solution {
    /**
     * 
     * @param n int整型
     * @param m int整型
     * @return int整型
     */
    public int ysf (int n, int m) {
        // write code here
        ArrayList<Integer> res = new ArrayList<>();
        for(int i = 1;i <= n;i++){
            res.add(i);//将n个人添加到链表中
        }
        int index = 0;
        while(res.size() > 1){
            index = (index + m - 1) % res.size();//判断删除的链表结点
            res.remove(index);//删除
        }
        return res.get(0);
    }
};

该算法需要遍历所有的元素,所以时间复杂度为,需要新建一个单链表对象,用于存放元素结点,所以其空间复杂度为