LeetCode 环形链表

93 阅读3分钟

「这是我参与2022首次更文挑战的第41天,活动详情查看:2022首次更文挑战」。

本节介绍的是LeetCode 141LeetCode 142两题环形链表的解题方法,实际上两题解题思路一致,只不过第142题相比于141题新增了一个返回入环节点。

题目:给定一个链表,判断该链表是否存在环,如果存在则返回入环点,否则返回null。

解题思路

判断链表是否存在环,常规的思路很简单,使用一个容器将环中所有节点依次保存下来,每次保存的时候判断当前容器是否存在此元素,如果存在则表明链表必定有环,如果链表可以访问到null,则表明链表必定没有环,这个数据结构在Java中使用的是HashSet,可得代码如下:

public boolean hasCycle(ListNode head) {
        HashSet<ListNode> set = new HashSet<>();
        while(head!=null){
            if(set.contains(head)){
                return true;
            }
            set.add(head);
            head = head.next;
        }
        return false;
    }

上述代码耗时4ms,时间复杂度和空间复杂度都是O(n)O(n)。下面介绍一种空间复杂度为O(1)O(1)的方法。

快慢指针

快慢指针指的是初始时存在两个指针指向同一个指针,一个指针为fast,另一个为slowfast指针每次走两步,slow指针每次走一步。

我们假设当前链表不存在环,则fast指针必然比slow指针先到null指针,此时可以通过判断fast是否为空来判断链表是否存在环。

如果当前链表存在环,则fast指针和slow指针必然会相遇。且相遇时slow指针没有走完一圈。

  1. 为什么会相遇?如果存在环,则fast指针必然比slow指针先入环,当slow到达入环点,此时fast开始追赶slow,假设当前fast距离slown,则fast走两步,距离变为n-2slow走一步,距离变为n-1,依次循环,距离必然会变为0
  2. 为什么相遇时slow没有走完一圈?假设当前链表不存在环外部分(即链表是一个首尾相连的环),此时slowfast相遇在slow刚好走完一圈。如果存在环外部分,则fast相比slow先入环,此时两个指针相遇slow必然没有到达一圈。

则可以得到如下代码:

public boolean hasCycle(ListNode head) {
        if(head==null||head.next==null) return false;
        ListNode fast = head;
        ListNode slow = head;
        while(fast!=null){
            if(fast.next!=null){
                fast = fast.next.next;
            }else{
                return false;
            }
            slow = slow.next;
            if(fast == slow){
                return true;
            }
        }
        return false;
    }

上述代码时间复杂度为O(n)O(n),空间复杂度为O(1)O(1)。解决了链表是否存在环,则第142题就简单了。

fastslow相遇后,假设相遇节点即为slow,从头节点cur开始和slow同时移动节点,当curslow相遇的时候的节点即为入环节点。

具体推导过程看这篇博客: 深入理解快慢指针算法 -- 链表环路检测 - 知乎 (zhihu.com)

可得代码如下:

public ListNode detectCycle(ListNode head) {
        if(head==null||head.next==null) return null;
        ListNode fast = head;
        ListNode slow = head;
        while(fast!=null){
            if(fast.next!=null){
                fast = fast.next.next;
            }else{
                return null;
            }
            slow = slow.next;
            if(fast == slow){
                ListNode cur = head;
                while(cur!=slow){
                    cur = cur.next;
                    slow = slow.next;
                }
                return cur;
            }
        }
        return null;
    }

时间复杂度为O(n)O(n),空间复杂度为O(1)O(1)