读书笔记——漫画算法(2) 有环链表

漫画算法:小灰的算法之旅,还算是一本挺有意思的书,值得一读

有环链表是一个很有意思的问题,这里讨论了三个问题:

  • 判断一个链表有环
  • 求环的长度
  • 求环的入口点,这个是最有意思,并且有点难度的问题

由于CSDN的编辑器对代码注释的排版的支持很low,我的字符画变乱了,并且移位了,这里我贴出完好的注释,下面的代码中如果有比较乱的注释,或者注释偏移了,可以参考如下的图片:
在这里插入图片描述
在这里插入图片描述
下面是完整的代码实现,代码的说明都在注释里写清楚了,很容易明白。

public class ListHasLoop {

    /**
     * 判断是否有环
     * @param head 链表的头节点
     * @return 如果有环,则返回值>0, 无环则返回0
     */
    public static int hasLoop(Node head) {
        Objects.requireNonNull(head);
        int counter = 0;
        Node p1 = head, p2 = head;
        while(p2 != null && p2.next != null) {
            p1 = p1.next;
            p2 = p2.next.next;
            counter++;
            if(p1 == p2) {
                return counter;
            }
        }
        return 0;
    }

    /**
     * 求环的入口节点
     *                S2
     *                   - - - - - -
     *                  /            \
     *                 /              \
     *                /                \
     *                \                / ←-- 首次相遇点
     *                 \              /  ↖
     *    - - - - - - - \ - - - - - -/    ↖
     *                                     ↖
     *                                      ↖
     *   ↑             ↑ ↖                   ↖
     *   |----> D <----|   ↖                  ↖
     *                       ← - - - - - - - - · S1
     *
     *   两个指针首次相遇时:
     *   p1所走距离为:       D + S1
     *   p2所走距离为:       n * (S1 + S2) + D + S1    (其中n>=1,为p2比p1多走的圈数)
     *
     *   由于p2的速度是p1的两倍,所以有如下的等式:
     *                      2 * (D + S1) = n * (S1 + S2) + D + S1
     *
     *   于是我们整理上面的等式:
     *                      D + S1 = n * (S1 + S2)
     *                      D = n * S1 - S1 + n * S2
     *                      D = (n - 1) * (S1 + S2) + S2
     *
     *   求得D的值为:
     *                      D = (n - 1) * (S1 + S2) + S2
     *
     *   现在我们需要分析一下这个D表达式的语义:
     *                      链表头节点到环入口的距离为:绕环转n-1圈后再走s2距离
     *
     *   那么我们现在可以这么求环的入口点:
     *                      将一个节点放置在首次相遇处,将一个节点放在头节点处,那么
     *                      这两个节点一步一步走,总会在环的入口点相遇
     *
     *   所以下面给出思路:
     *   *******************************************************************************
     *   *                   求p1, p2首次相遇的节点,然后将p1置为头节点,                   *
     *   *                   p2置为相遇点,两者都一步一步走                                *
     *   *******************************************************************************
     * @param head
     * @return
     */
    public static Node loopEntryNode(Node head) {
        Pair info = getFirstMeetInfoInLoop(head);
        if(info.firstMeetNode == null || info.loopLength <= 0) {
            return null;
        }
        Node p1 = head, p2 = info.firstMeetNode;
        while(true) {
            if(p1 == p2) {
                return p1;
            }
            p1 = p1.next;
            p2 = p2.next;
        }
    }

    public static Pair getFirstMeetInfoInLoop(Node head) {
        Objects.requireNonNull(head);
        int counter = 0;
        Node p1 = head, p2 = head;
        while(p2 != null && p2.next != null) {
            p1 = p1.next;
            p2 = p2.next.next;
            counter++;
            if(p1 == p2) {
                return new Pair(p1, counter);
            }
        }
        return new Pair(null, 0);
    }

    /**
     * 这里我们定义一个Pair
     * 可以存放首次相遇的节点,以及环的长度
     * 
     * 后续如果一次遍历需要获取比较多的信息,就可以用此数据结构
     */
    private static class Pair {
        Node firstMeetNode;
        int loopLength;

        public Pair(Node firstMeetNode, int loopLength) {
            this.firstMeetNode = firstMeetNode;
            this.loopLength = loopLength;
        }
    }

    /**
     *                1 ←  8
     *                ↓    ↑
     * 5 -> 3 -> 7 -> 2 -> 6
     *
     * @param args
     */
    public static void main(String[] args) {
        Node node1 = new Node(5);
        Node node2 = new Node(3);
        Node node3 = new Node(7);
        Node node4 = new Node(2);
        Node node5 = new Node(6);
        Node node6 = new Node(8);
        Node node7 = new Node(1);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        node5.next = node6;
        node6.next = node7;
        node7.next = node4;
        // output:Node{data=2}
        System.out.println(loopEntryNode(node1));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值