【劍指Offer】55、鏈表中環的入口結點


  題目描述:

  給一個鏈表,若其中包含環,請找出該鏈表的環的入口結點,否則,輸出null。

  解題思路:

  本題是一個比較典型的鏈表題目,難度適中。首先,對於大多人來說,看到這道題是比較開心的,因為判斷一個鏈表是否存在環的方法,基本上大家都知道,就是快慢指針法,但是再仔細一看,本題除了判斷是否有環之外,還要找到這個環的入口點,這就略有些復雜了。

  具體思路如下:

  第一步:確定一個鏈表是否有環。這一步就是快慢指針法,定義兩個指針,同時從鏈表的頭結點出發,快指針一次走兩步,慢指針一次走一步。如若有環,兩個指針必定相遇,也就是如果快指針反追上了慢指針,說明存在環(這里要注意,兩指針相遇的地方一定在環中,但不一定是環的入口),如果快指針走到了鏈表的末尾(指向了NULL),則說明不存在環。

  第二步:找到環的入口點。這還是可以利用雙指針來解決,兩個指針初始都指向頭結點,如果我們可以知道環中的結點個數,假設為n,那么第一個指針先向前走n步,然后兩個指針(另一個從頭結點開始)同時向前,當兩個指針再次相遇時,他們的相遇點正好就是環的入口點。

  這其實並不難理解,假設鏈表中共有m個結點,環中有n個結點,那么除環以外的結點數就是m-n,第一個指針先走了n步,然后兩個指針一起向前,當他們一起向前m-n步時,第一個鏈表正好走完一遍鏈表,返回到環的入口,而另一個指針走了m-n步,也正好是到了環的入口。

  現在,我們還有一個關鍵的問題:如何知道鏈表中的環包含了幾個結點呢?也就是,怎么求這個n。

  實際上這也不難,在第一步中,我們提到:快慢指針相遇的地方一定在環中,並且通過第一步我們已經找到了這個位置,接下來,只要從這個相遇的結點出發,一邊移動一邊計數,當它繞着環走一圈,再次回到這個結點時,就可以得到環中的結點數目n了。

  舉例:

  編程實現(Java):

public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}

public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead){
        if(pHead==null||pHead.next==null)
            return null;
        ListNode meet=meetingNode(pHead); //相遇的節點
        if(meet==null)
            return null;
        //求環的長度
        ListNode temp=meet;
        int len=1;
        temp=temp.next;
        while(temp!=meet){
            temp=temp.next;
            len++;
        }
        
        ListNode fast=pHead,slow=pHead;
        //快指針先走len步
        for(int i=0;i<len;i++)
            fast=fast.next;
        while(slow!=fast){
            slow=slow.next;
            fast=fast.next;
        }
        return slow;
    }
    
    //判斷是否有環,返回相遇的節點,思路:快慢指針,若有環必相遇
    public ListNode meetingNode(ListNode pHead){
        if(pHead==null||pHead.next==null)
            return null;
        ListNode fast=pHead,slow=pHead;
        while(fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
            if(slow==fast)
                return slow;
        }
        return null;
    }
}

  通過查看相關文章發現,實際上,當找到在環中相遇的結點meetingNode時,一個指針指向meetingNode,另一個指針指向head,然后一起向前,這兩個指針相遇的位置就一定是環的入口點,這個證明比較復雜,但實際上這和先走n步的解法本質上還是一樣的,只不過是在環里面多繞幾圈。可以參考一下 https://zhuanlan.zhihu.com/p/85349101 的證明過程。

public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead) {
        //鏈表中環的入口結點
        if(pHead==null)
            return null;
        ListNode meetingNode=hasCycle(pHead);
        if(meetingNode==null)  //不存在環
            return null;
        
        ListNode head=pHead;
        while(head!=meetingNode){
            head=head.next;
            meetingNode=meetingNode.next;
        }
        return head;
    }

    public ListNode hasCycle(ListNode pHead){
        //判斷是否有環,快慢指針法
        if(pHead==null || pHead.next==null)
            return null;
        ListNode slow=pHead,fast=pHead;
        while(fast!=null && fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
            if(slow==fast)
                return slow;
        }
        return null;
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM