约瑟夫环基本和进阶解法

/**
 * 约瑟夫环问题
 * 就是有一堆人连成一个环 从某一个位置开始 数到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;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值