題目
輸入一個鏈表,輸出該鏈表中倒數第k個結點。為了符合大多數人的習慣,本題從1開始計數,即鏈表的尾結點是倒數第1個結點。例如一個鏈表有6個結點,從頭結點開始它們的值依次是1、2、3、4、5、6。這個鏈表的倒數第3個結點是值為4的結點。
解題思路
1.不可行的常規解法
為了得到倒數第k個結點,很自然的想法是先走到鏈表的尾端,再從尾端回溯k步。當時,從鏈表結點的定義可以看出本題中的鏈表是單向鏈表,單向鏈表的結點只有從前往后的指針而沒有從后往前的指針,因此這種思路行不通,它只適用於雙向鏈表。
如果鏈表定義中有指向前一個節點的指針,那么此解法是可行的
2.可行但不高效的常規解法
假設整個鏈表有n個結點,那么倒數第k個結點就是從頭結點開始的第n-k+1個結點。如果我們能夠得到鏈表中結點的個數n,那我們只要從頭結點開始往后走n-k+1步就可以了。
那么,這里的重點就在於如何求鏈表中節點的個數n,只需要從頭開始遍歷鏈表,每經過一個結點,計數器加1就行了。
但是,問題來了:這種思路需要遍歷鏈表兩次,第一次統計出鏈表中結點的個數,第二次才能找到倒數第k個結點。
3.可行且高效的解法
為了能夠只遍歷一次就能找到倒數第k個節點,可以定義兩個指針:
(1)第一個指針從鏈表的頭指針開始遍歷向前走k-1,第二個指針保持不動;
(2)從第k步開始,第二個指針也開始從鏈表的頭指針開始遍歷;
(3)由於兩個指針的距離保持在k-1,當第一個(走在前面的)指針到達鏈表的尾結點時,第二個指針(走在后面的)指針正好是倒數第k個結點。
下圖展示了在有6個結點的鏈表上找倒數第3個結點的過程:
拓展
- 當我們用一個指針遍歷鏈表不能解決問題的時候,可以嘗試用兩個指針來遍歷鏈表。可以讓其中一個指針遍歷的速度快一些(比如一次在鏈表上走兩步),或者讓它先在鏈表上走若干步。
- 求鏈表的中間節點如果鏈表的結點總數為奇數,返回中間節點,如果鏈表的總數為偶數,返回中間兩個結點中的任意一個也可以定義兩個指針,同時從鏈表的頭出發,一個走一步,一個走兩步,走的快的到表尾時,走得慢的正好是中間結點。
- 當用一個指針遍歷鏈表不能解決問題的話,嘗試用兩個鏈表
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } };*/ class Solution { public: ListNode* FindKthToTail(ListNode* head, unsigned int k) { if(!head) return nullptr; ListNode *fast=head,*slow=head; while(k--) { /*果走不到第k步(nullptr節點是可以走到的,但是nullptr節點沒有next,所以只能走到nullptr), 說明鏈表長度不夠k,直接返回nullptr*/ if(fast==nullptr) return nullptr; fast=fast->next; } while(fast) { fast=fast->next; slow=slow->next; } return slow; } };
解決代碼魯棒性
(1)輸入的head為空指針。由於代碼會試圖訪問空指針指向的內存,程序崩潰。
解決:在處理前增加判斷空指針的代碼
(2)輸入的以head為頭結點的鏈表的結點總數少於k。由於在for循環中會在鏈表上向前走k-1步,仍然會由於空指針造成程序崩潰。
解決:在for循環中增加判斷下一個節點是否是空指針的代碼
(3)輸入的參數k為0。由於k是一個無符號整數,那么在for循環中k-1得到的將不是-1,而是4294967295(無符號的0xFFFFFFFF)。因此for循環執行的次數遠遠超出我們的預計,同樣也會造成程序崩潰。