/**
* 约瑟夫环问题
* 就是有一堆人连成一个环 从某一个位置开始 数到3的被杀死 然后从下一个从1开始数
* 以此类推 最后剩下一个人 返回这个人。
*
* 问题抽象:
* 其实就是一个环形单链表问题
* 输入:一个头节点和报数的值m
* 返回:最后生存下来的节点自己成环
* 进阶:如果想在事件复杂度为O(N)的条件下解法
* */
public class JosephusKill {
/*
* 普通解法:
* 1.如果链表为空或链表节点数为1或m的值小于1 就直接返回头节点
* 2.环形链表中不断循环让每个节点不断的进行报数
* 3.当报数为m删除该节点 并调整为新的环形并继续报数
* 4.最后剩下一个节点返回 结束
*
* 因为没删除一个节点都要遍历m次 一共需要删除的节点为n-1个 所以其时间复杂度为O(m * n)
* */
public Node josephusKill1(Node head, int m) {
if(head == null || head.next == head || m < 1) {
return head;
}
Node last = head;
while (last.next != head){
last = last.next;
}
int count = 0;
while(head.next != last) {
if(++count == m) {
last.next = head.next;
count = 0;
} else {
last = last.next;
}
head = head.next;
}
return head;
}
/*
* 时间复杂度为O(n)的解法
* 分析上面这个解法 因为我们不知道哪个节点最终会活下来 所以只能靠不停的删除来淘汰节点
* 当剩下最后一个节点的时候我们才知道这个是哪个节点 如果不通过一直删除的方式有没有办法
* 能够直接确定最后活下来的是哪个节点呢?这个就是进阶解法的实质,举一个例子:
* 1->2->3->4->5->1
* 这个链表的n为5 m为3 通过不断删除的方式 最后是4活下来了。但是我们可以不通过不断删除
* 的方式而是通过进阶的方法,根据n和m的值,直接算出来是第四个节点会活下来,接下来找到4
* 节点即可。
* 怎么算出来的呢?? 因为我们假设最后没删除的节点数的编号一定为Num(1) 两个节点中的编号一定为2
* 在第i-1个节点中编号 假设其幸运编号为Num(i-1) 第i个中为Num(i) ...以此类推在n个节点
* 组成的环中假设其编号为Num(n)
*
* 1.我们已知Num(1) = 1;如果再确定Num(i-1)和Num(i)之间的关系 就可以利用递归过程求出Num(n)
* A B 从左边可以看出A和B之间的关系是B = (A-1)%i + 1--这个表达式不唯一
* 1 1
* 2 2
* ...
* i i
* i+1 1
* i+2 2
* ...
* 2i+11
* ...
*2.如果节点编号为s的节点被删除,环的接节点数自然从i变为了i-1,那么每个节点的编号该怎么变化呢
* 环大小为i时的每个节点的编号 删掉s之后 环大小为i-1的每个节点的编号
* ... ...
* s-2 i-2
* s-1 i-1
* s i
* s+1 i+1
* s+2 i+2
* ... ...
* 因为删除s之后 新环只有i-1个节点 s+1 s+2。。。变成了第一个节点 第二个节点...
* 编号为s的前一个节点变为新环的最后一个节点,也就是编号为i-1的节点
* 假设环大小为i的节点的编号为old 环大小为i-1的节点编号为new ,则old于new之间的关系的
* 数学表达式为;old = (new + s -1)%i + 1 表达式不止一种
* 3.因为每次报数到m的节点被杀,所以按照步骤一的表达式B=(A-1)%i+1,A=m。
* 被杀的节点编号为(m-1)%i+1 及s=(m-1)%i+1,带入到步骤2的表达式old=(new+s-1)%i+1
* 经过简化为old = (new + m -1)%i +1(其实就是对应的递推关系式:f(n,m) = (f(n,m-1) + m) % n)
* 我们终于得到了Num(i-1)--new Num(i)--old的关系 而且这个关系只与m和i的值有关
* -------------------------------------------------------------------
* 总结整个进阶解法的过程:
* 1.遍历链表,求链表的节点个数n --时间复杂度为O(n)
* 2.根据m和n的值 还有上面分析的Num(i-1)、Num(i)之间的关系,递归可求出
* 生存节点的编号;这一步解法详见,getLive的方法,getLive方法为单决策的递归函数 且递归层为n
* 层,所以时间复杂度为O(n)
* 3.最后根据生存节点的编号,遍历链表找到该节点,时间复杂度为O(n)
* 4.整个过程结束 总的时间复杂度为O(n)
* */
public Node josephusKill2(Node head, int m) {
if(head == null || head.next == head || m < 1){
return head;
}
Node cur = head.next;
int tmp = 1;//temp为链表的长度
while (cur != head){
tmp++;
cur = cur.next;
}
tmp = getLive(tmp, m);//tmp为活着的那个节点
while (--tmp != 0){
head = head.next;
}
head.next = head;
return head;
}
public int getLive(int i, int m) {
if(i == 1) {
return 1;
}
return (getLive(i - 1, m) + m -1) % i + 1;
}
}
public class Node {
public int value;
public Node next;
public Node(int data){
this.value = data;
}
}
约瑟夫环基本和进阶解法
最新推荐文章于 2024-08-11 21:58:53 发布