链表中环的问题

算法通关村第一关 —— 链表黄金挑战笔记

本文就关于环的两个问题进行讨论

链表是否有环

确定环的入口

测试代码

public static void main(String[] args) {
        Node<Integer> one = new Node<>(11);
        Node<Integer> two = new Node<>(22);
        Node<Integer> three = new Node<>(33);
        Node<Integer> four = new Node<>(44);
        Node<Integer> five = new Node<>(55);
        Node<Integer> six = new Node<>(66);
        Node<Integer> seven = new Node<>(77);
        Node<Integer> eight = new Node<>(88);

        Link link = Struct.getRang(new Node[]{one, two, three, four, five, six, seven},eight);
        System.out.println(isRangHash(link));
        System.out.println(isRang(link));
        System.out.println(getStart().getVal());
}
public static Link<Integer> getRang(Node<Integer>[] nodes,Node<Integer> start) {
        Link<Integer> link = new Link<>();
        Arrays.asList(nodes).forEach(link::append);
        link.add(link.getHead(),start, link.getLength() - 1);
        return link;
}

链表是否有环

方法一

        把链表中的元素放到hash中,如果存在环,则一定会碰撞

    /**
     * 是否环
     * 使用hash
     **/
    public static boolean isRangHash(Link<Integer> link) {
        if (Struct.isLinkBlank(link)){
            return false;
        }
        HashSet<Node<Integer>> nodes = new HashSet<>();
        Node<Integer> node = link.getHead();
        while (node != null){
            nodes.add(node);
            node = node.getNext();
            //存在则会发生碰撞
            if (nodes.contains(node)){
                return true;
            }
        }
        return false;
}

方法二

        使用快慢指针,慢指针每次走一步,快指针每次走两步,若链表存在环,则快慢指针必会相遇

        因为快指针只比满指针快一步,当满指针到打环的起点时,快指针必定在环的某个节点上,此时快指针则会一步一步的向慢指针追去,直至相遇

    /**
     * 是否环
     * 使用快慢指针,慢指针每次走一步,快指针每次走两步,若链表存在环结构,则必然会相交
     **/
    public static boolean isRang(Link<Integer> link) {
        if (Struct.isLinkBlank(link)){
            return false;
        }

        Node<Integer> slow = link.getHead();
        Node<Integer> fast = link.getHead();
        //fast.getNext() != null 链表长度 = 1
        while (fast != null && fast.getNext() != null){
            slow = slow.getNext();
            fast = fast.getNext().getNext();
            //相遇则为环
            if (slow == fast){
                return true;
            }
        }
        //若链表不存在环,快指针会先到达链尾
        return false;
    }

确定入口

         如图,先说方法,同样是快慢指针,两指针同时开始前进,记录第一次相遇的节点,然后两指针分别于头节点和相遇节点以step = 1的速度向前,当他们再次相遇的同时,就是环的入口

        换成字母描述,满指针slow,快指针fast,slow和fast同时从x出发,在z点相遇,然后slow重新回到x点,fast则从z点开始,同时以同样的速度(step = 1)前进,最终第一次相遇必会在Y(环入口)处;

         这是为什么呢?看起来似乎是这么回事,但又有哪里说不上来的样子

         别急,我们从数学的角度分析一下,该场景是否合理;

         此时,我们不妨把该场景换成一道数学的证明题:

         如上图,|XY| = a,|YZ| = b,|ZY| = c,设环的长度d = |YZ| + |ZY| = b + c

         ∵环两节点必会相遇,∴2(a + b) = a + b + nd  (n∈Z & n > 1)

          证明:a = c + md (m,n ∈ Z)

        证:2(a + b) = a + b + nd 

                       2a  = a - b + nd

                       a    =  - b + (n-1)d + d

                       a    =  - b + b + c + (n-1)d

                       a    =  c + (n-1)d

        令m =  n-1 ,证得 a = c + md

其中n和m分别是相遇前和第二次同速度开始跑的fast在环中跑的次数,可以看出,当b < d,这时fast会比一开始的时候少走一圈;

    /**
     * 环的起点
     * 快慢指针先走到相遇点,然后慢指针回到头节点,快指针仍在相遇点,然后两指针以step=1的速度前进,相遇时即为起点
     */
    public static Node<Integer> getStart(Link<Integer> link) {
        if (Struct.isLinkBlank(link)) {
            return null;
        }
        Node<Integer> slow = link.getHead().getNext();
        Node<Integer> fast = link.getHead().getNext().getNext();

        //前提条件:链表一定存在环,长度大于2
        while (fast != slow) {
            slow = slow.getNext();
            fast = fast.getNext().getNext();
        }
        //慢指针重新回到头节点
        slow = link.getHead();
        while (fast != slow) {
            slow = slow.getNext();
            fast = fast.getNext();
        }
        return fast;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值