参考--剑指offer:圆圈中最后剩下的数字
https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/si-chong-fang-fa-xiang-xi-jie-da-by-yuanninesuns/
解法1:数学 + 迭代+递归
反推过程:(当前index + m) % 上一轮剩余数字的个数
最终剩下一个人时的安全位置肯定为1,反推安全位置在人数为n时的编号
人数为1: 0+1
人数为2: (0+m) % 2+1
人数为3: ((0+m) % 2 + m) % 3+1
人数为4: (((0+m) % 2 + m) % 3 + m) % 4+1
........
迭代推理到n就可以得出答案
public int ysf(int n, int m){ if (m < 1 || n < 1) return -1; int last = 0; // i代表有目前有个人 //最后一轮剩下2个人,所以从2开始反推 for (int i = 2; i <= n; i++) last = (last + m) % i; return last+1; }
递归
public int ysf (int n, int m) { // write code here if(n < 1 || m < 1) return 0; if(n == 1) return 1; return (ysf(n-1,m)+m-1)%n + 1; } }
解法2:链表
将[1,n]依次存储在链表中
只要链表的长度不为1,就一直循环,如果到了第m个就remove;否则将其添加到链表尾部
时间复杂度为O(nm)
public int LastRemaining(int n, int m){ LinkedList<Integer> list=new LinkedList<>(); if(m<1 || n<1){ return -1; } for(int i=0;i<n;i++){ list.add(i); } int bt=0; while(list.size()>1){ bt=(bt+m-1)%list.size(); list.remove(bt); } return list.get(0)+1; }
解法3:单向链表
转载:https://blog.csdn.net/qq_45702990/article/details/107326453
① 构造单向环形列表
思路:
先创建第一个节点,让 first 指向该节点,并构建成环形
后面每创建一个新的节点,将该节点加入到已有的环形链表中即可
图示:
代码如下:
/** * 构建单向环形链表 * @param nums 队列人数 */ public void circle(int nums) { //数据校验,如果传入的数据不合法,提示并结束方法 if (nums < 1) { System.out.println("nums的值不正确!无法创建环!"); return; } //初始化中间节点 Node temp = null; for (int i = 1; i <= nums; i++) { //用来存放每次新创建的编号节点 Node node = new Node(i); //如果是第一个节点,则直接赋值给first,且构建成环状 if (i == 1) { first = node; first.next = first; temp = first; continue; } //如果不是第一个节点,则将其添加进环中 temp.next = node; node.next = first; temp = node; } }
② 遍历环形链表
思路:
先让一个辅助节点 temp 指向 first节点
然后通过一个 while 循环遍历该环形链表即可(如果 temp.next==first 则结束循环)
代码如下:
/** * 遍历打印环形链表 */ public void list() { //如果第一个节点没有数据,则链表为空,提示并结束方法 if (first == null) { System.out.println("链表为空!"); return; } //借助辅助节点遍历 Node temp = first; while (temp.next != first) { System.out.println(temp); temp = temp.next; } System.out.println(temp); }
③约瑟夫环问题解决
思路:
先创建一个辅助节点 temp 指向当前链表的最后一个节点
在开始报数前,先让 temp 与 first 同时移动 k-1 次
当开始报数时,让 temp 与 first 同时移动 m-1 次
将 first 指向的编号出列,first 指向出列节点的下一个节点
图示:
代码如下:
/** * 计算约瑟夫环的出列序列 * @param k 第k个节点开始 * @param m 开始后第m个节点出列 * @param n 一共有n个节点 */ public void joseph(int k, int m, int n) { //数据校验,如果数据不合法则提示且结束方法 if (first == null || k < 1 || k > n) { System.out.println("数据不合法!"); return; } //辅助节点 Node temp = first; //找到环形链表的最后一个节点(下一个节点为 first 节点的节点),并赋值给辅助节点 while (temp.next != first) { temp = temp.next; } //在开始报数前,先让 temp 与 first 同时移动 k-1 次 for (int i = 0; i < k - 1; i++) { first = first.next; temp = temp.next; } //当 temp == first 时,环形链表中只剩下一个节点,结束循环 while (temp != first) { //找到报数为 m 的节点 for (int i = 0; i < m - 1; i++) { first = first.next; temp = temp.next; } //输出当前节点编号,且将当前节点出列 System.out.printf("%d-->", first.id); first = first.next; temp.next = first; } //输出最后节点编号 System.out.println(first.id); }