参考--剑指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);
}
京公网安备 11010502036488号