几个和带环链表有关的问题

535 阅读3分钟

2021/04/22

前言

image.png

如下链表是一个带环链表,环的入口是 2,环的长度为 4。

image.png

链表节点定义如下:

// 单链表节点定义,参考 Leetcode
class ListNode<T> {
    public T val;
    public ListNode<T> next;

    public ListNode(){
        this(null,null);
    }

    public ListNode(T val){
        this(val,null);
    }

    public ListNode(T val, ListNode next){
        this.val = val;
        this.next = next;
    }
}

判断链表是否有环

判断链表是否有环,比较好的方法是 快慢指针法

定义两个指针 fast, slow ,初始都指向头节点。快指针 fast 一次向后移动一位,慢指针 slow 一次向后移动两位;如果快慢指针相遇(指向同一个节点且都不为空),说明链表存在环。

// 判断链表是否有环
public boolean isLoop(){
    if(this.head == null){
        return false;
    }
    ListNode<T> fast = head;
    ListNode<T> slow = head;
    while(fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if(fast == slow){
            return true;
        }
    }
    return false;
}

确定环入口

如果链表存在环,如何确定环的入口(环交汇的节点)是哪个节点?

假设链表环的入口为 entrance ,快慢指针相遇的节点为 meet,由于快指针的速度是慢指针的 2 倍,所以当快慢指针相遇时,慢指针走过的距离为 s,则快指针走过的距离为 2s

假设头节点到环入口的距离为 he,环入口到相遇节点的距离为 em,环的长度为 r

image.png

我们可以得到:

s=he+em2s=he+em+nrs = he + em \\ 2s = he + em + n*r

其中,n 表示快指针走过的环的圈数。所以 s=nrs = n*r ,即相遇时,慢指针走过的距离是环长度的整数倍。

假设链表的长度为 L,有 L = he + r,则

s=he+em=nr=(n1)r+r=(n1)r+Lhes = he + em = n*r = (n-1)*r + r = (n-1)*r + L - he

得到

he=(n1)r=Lheem=(n1)r+remhe = (n-1)*r = L - he - em = (n-1)*r + r - em

如果从相遇点和头节点以相同的速度向后移动,走经过 n-1 个环,能够在环入口相遇

所以我们得到如何得到环入口的方法:

  • 找到快慢指针相遇的节点 meet
  • 定义两个指针,分别指向 headmeet
  • 双指针同时向后移动,直到两个指针指向同一个节点,这个节点就是环的入口
 public ListNode getLoopEntrance(){
    if(this.head == null){
        return null;
    }
    ListNode<T> meet = null;
    ListNode<T> fast = head;
    ListNode<T> slow = head;
    while(fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if(fast == slow){
            meet = fast;
            break;
        }
    }
    // meet == null 说明没有相遇即链表没有环
    if(meet == null){
        return null;
    }
    // 快慢指针从相遇点开始以相同的速度向后移动
    fast = head;
    slow = meet;
    while(fast != slow){
        fast = fast.next;
        slow = slow.next;
    }
    return fast;
}

确定环的长度

在一个环上,快慢指针速度相差两倍。从相遇节点开始,快慢指针依次按照各自的速度向后移动,慢指针移动一次长度增加 1 ,当快慢指针相遇时,慢指针走过的距离就是环的长度。

public int getLoopLength() {
    if (head == null || head.next == null) {
        return -1;
    }
    ListNode<T> fast = head.next;
    ListNode<T> slow = head.next;
    boolean isLoop = false;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) {
            isLoop = true;
            break; //此时fast和slow第一次相遇
        }
    }
    // 如果链表没有环,返回 0
    if(!isLoop){
        return 0;
    }
    // 已经相遇,len 初始为 1 ,快慢指针继续向后移动
    int len = 1;
    slow = slow.next;
    fast = fast.next.next;
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next.next;
        len++;
    }
    return len;
}