牛客網《劍指Offer》(14)鏈表中倒數第k個節點

題目描述

輸入一個鏈表,輸出該鏈表中倒數第k個結點。

題目分析

鏈表的特徵就是修改方便,查找複雜,不支持隨機查找,更何況倒數第k個節點,除非是雙向鏈表。

此題解題其實是有一定技巧的,我們使用快慢指針來實現。同樣類似的問題還有好多,我們先解題,下文會列出所有類似問題。

1、設立兩個指針(快指針與慢指針),都指向鏈表頭部。

2、將快指針前移k個位置。

3、同時移動快慢指針,當快指針移到鏈表尾部時,慢指針指向的便是鏈表倒數第k個位置。

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        ListNode* quick = pListHead;
        ListNode* slow = pListHead;
       
        //邊界值:空鏈表
        if(pListHead == NULL)
            return NULL;
        
        //邊界值:k = 0
        if( 0 == k )    //編碼規範:k == 0 容易寫錯成 k = 0;所以習慣寫爲: 0 == k;
            return NULL;
               
        while(quick != NULL && k >0)
        {
            quick = quick -> next;
            k--;
        }
        
        //邊界值:k 大於鏈表長度
        if(quick == NULL && k>0) 
            return  NULL;
        
        while(quick != NULL)
        {
            quick = quick -> next;
            slow = slow -> next;
        }
       
        return slow;
    }
};

鏈表相關類似問題

(轉載自碼農有道公衆號:https://mp.weixin.qq.com/s/jJQTykI02oc5Hc6ObPFbmQ

一、在O(1)時間刪除鏈表節點

題目描述:給定鏈表的頭指針和一個節點指針,在O(1)時間刪除該節點。[Google面試題]

分析:本題與《編程之美》上的「從無頭單鏈表中刪除節點」類似。主要思想都是「狸貓換太子」,即用下一個節點數據覆蓋要刪除的節點,然後刪除下一個節點。但是如果節點是尾節點時,該方法就行不通了。

代碼:如下

二、求鏈表中間節點

題目描述:求鏈表的中間節點,如果鏈表的長度爲偶數,返回中間兩個節點的任意一個,若爲奇數,則返回中間節點。

分析:此題的解決思路和第3題「求鏈表的倒數第 k 個節點」很相似。可以先求鏈表的長度,然後計算出中間節點所在鏈表順序的位置。但是如果要求只能掃描一遍鏈表,如何解決呢?最高效的解法和第3題一樣,通過兩個指針來完成。用兩個指針從鏈表頭節點開始,一個指針每次向後移動兩步,一個每次移動一步,直到快指針移到到尾節點,那麼慢指針即是所求。

代碼:如下

三、判斷單鏈表是否存在環

題目描述:輸入一個單向鏈表,判斷鏈表是否有環?

分析:通過兩個指針,分別從鏈表的頭節點出發,一個每次向後移動一步,另一個移動兩步,兩個指針移動速度不一樣,如果存在環,那麼兩個指針一定會在環裏相遇。

代碼:如下

四、找到環的入口點

題目描述:輸入一個單向鏈表,判斷鏈表是否有環。如果鏈表存在環,如何找到環的入口點?

分析:由上題可知,按照 p2 每次兩步,p1 每次一步的方式走,發現 p2 和 p1 重合,確定了單向鏈表有環路了。接下來,讓p2回到鏈表的頭部,重新走,每次步長不是走2了,而是走1,那麼當 p1 和 p2 再次相遇的時候,就是環路的入口了。

爲什麼:假定起點到環入口點的距離爲 a,p1 和 p2 的相交點M與環入口點的距離爲b,環路的周長爲L,當 p1 和 p2 第一次相遇的時候,假定 p1 走了 n 步。那麼有:p1走的路徑: a+b = n;p2走的路徑: a+b+k*L = 2*n; p2 比 p1 多走了k圈環路,總路程是p1的2倍

根據上述公式可以得到 k*L=a+b=n顯然,如果從相遇點M開始,p1 再走 n 步的話,還可以再回到相遇點,同時p2從頭開始走的話,經過n步,也會達到相遇點M。

顯然在這個步驟當中 p1 和 p2 只有前 a 步走的路徑不同,所以當 p1 和 p2 再次重合的時候,必然是在鏈表的環路入口點上。

代碼:如下

五、判斷兩個鏈表是否相交

題目描述:給出兩個單向鏈表的頭指針(如下圖所示),判斷這兩個鏈表是否相交。這裏爲了簡化問題,我們假設兩個鏈表均不帶環。

分析

1、直接循環判斷第一個鏈表的每個節點是否在第二個鏈表中。但,這種方法的時間複雜度爲O(Length(h1) * Length(h2))。顯然,我們得找到一種更爲有效的方法,至少不能是O(N^2)的複雜度。

2、針對第一個鏈表直接構造hash表,然後查詢hash表,判斷第二個鏈表的每個節點是否在hash表出現,如果所有的第二個鏈表的節點都能在hash表中找到,即說明第二個鏈表與第一個鏈表有相同的節點。時間複雜度爲爲線性:O(Length(h1) + Length(h2)),同時爲了存儲第一個鏈表的所有節點,空間複雜度爲O(Length(h1))。是否還有更好的方法呢,既能夠以線性時間複雜度解決問題,又能減少存儲空間?

3、轉換爲環的問題。把第二個鏈表接在第一個鏈表後面,如果得到的鏈表有環,則說明兩個鏈表相交。如何判斷有環的問題上面已經討論過了,但這裏有更簡單的方法。因爲如果有環,則第二個鏈表的表頭一定也在環上,即第二個鏈表會構成一個循環鏈表,我們只需要遍歷第二個鏈表,看是否會回到起始點就可以判斷出來。這個方法的時間複雜度是線性的,空間是常熟。

4、進一步考慮“如果兩個沒有環的鏈表相交於某一節點,那麼在這個節點之後的所有節點都是兩個鏈表共有的”這個特點,我們可以知道,如果它們相交,則最後一個節點一定是共有的。而我們很容易能得到鏈表的最後一個節點,所以這成了我們簡化解法的一個主要突破口。那麼,我們只要判斷兩個鏈表的尾指針是否相等。相等,則鏈表相交;否則,鏈表不相交。

所以,先遍歷第一個鏈表,記住最後一個節點。然後遍歷第二個鏈表,到最後一個節點時和第一個鏈表的最後一個節點做比較,如果相同,則相交,否則,不相交。這樣我們就得到了一個時間複雜度,它爲O((Length(h1) + Length(h2)),而且只用了一個額外的指針來存儲最後一個節點。這個方法時間複雜度爲線性O(N),空間複雜度爲O(1),顯然比解法三更勝一籌。

代碼:如下

六、兩個鏈表相交的第一個公共節點

題目描述:如果兩個無環單鏈表相交,怎麼求出他們相交的第一個節點呢?

分析:採用對齊的思想。計算兩個鏈表的長度 L1 , L2,分別用兩個指針 p1 , p2 指向兩個鏈表的頭,然後將較長鏈表的 p1(假設爲 p1)向後移動L2 - L1個節點,然後再同時向後移動p1 , p2,直到 p1 = p2。相遇的點就是相交的第一個節點。

代碼:如下

七、鏈表有環,如何判斷相交

題目描述:上面的問題都是針對鏈表無環的,那麼如果現在,鏈表是有環的呢?上面的方法還同樣有效麼?

分析:如果有環且兩個鏈表相交,則兩個鏈表都有共同一個環,即環上的任意一個節點都存在於兩個鏈表上。因此,就可以判斷一鏈表上倆指針相遇的那個節點,在不在另一條鏈表上。

代碼:如下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章