劍指Offer面試題15(Java版):鏈表中倒數第K個結點
為了符合大多數人的習慣,本題從1開始計數。即鏈表的尾結點是倒數第1個結點。
比如一個鏈表有6個結點。從頭結點開始它們的值依次是1。2。3,4,5,6.這個鏈表的倒數第3個結點是值為4的結點
為了得到第K個結點,非常自然的想法是先走到鏈表的尾端。再從尾端回溯K步。
但是我們從鏈表結點的定義可疑看出本題中的鏈表 是單向鏈表,單向鏈表的結點僅僅有從前往後的指針而沒有從後往前的指針,因此這樣的思路行不通。
既然不能從尾節點開始遍歷這個鏈表。我們還是把思路回到頭結點上來。
如果整個鏈表有N個結點,那麽倒數第K哥結點就是從頭結點開始的第n-k-1個結點。如果我們僅僅要從頭結點開始往後走n-k+1步就可疑了。怎樣得到節點數n?
這個不難,僅僅須要從頭開始遍歷鏈表,沒經過一個結點,計數器加1即可了。
也就是說我們須要遍歷鏈表兩次,第一次統計出鏈表中結點的個數。第二次就能找到倒數第k個結點。可是當我們把這個思路解釋給面試官之後,他會告訴我們他期待的解法僅僅須要遍歷鏈表一次。
為了實現僅僅遍歷鏈表一次就能找到倒數第k個結點。我們能夠定義兩個指針。第一個指針從鏈表的頭指針開始遍歷向前走k-1。第二個指針保持不動;從第k步開始,第二個指針也開化寺從鏈表的頭指針開始遍歷。
因為兩個指針的距離保持在k-1。當第一個(走在前面的)指針到達鏈表的尾指結點時,第二個指針正好是倒數第k個結點。
不少人在面試前從網上看到過這道用兩個指針遍歷的思路來解答這道題。因此聽到面試官的這道題,他們心中一喜,非常快就寫出了代碼。但是幾天後等來的不是Offer,而是拒信。於是百思不得其解。事實上原因非常easy。就是自己的代碼不夠魯棒。
面試官能夠找出3種方法讓這段代碼崩潰。
1、輸入Head指針為Null。
因為代碼會試圖訪問空指針指向的內存,程序會崩潰。
2、輸入以Head為頭結點的鏈表的結點總數少於k。因為在for循環中會在鏈表向前走k-1步。仍然會因為空指針造成崩潰。
3、輸入的參數k為0.或負數,相同會造成程序的崩潰。
這麽簡單的代碼存在3哥潛在崩潰的風險,我們能夠想象當面試官看到這種代碼會是什麽心情。終於他給出的是拒信而不是Offer。
下面我們給出Java版的代碼:
/** * 輸入一個鏈表。輸出該鏈表中倒數第k哥結點。 * 為了符合大多數人的習慣,本題從1開始計數,即鏈表的尾結點是倒數第1個結點。 * 比如一個鏈表有6個結點。從頭結點開始它們的值依次是1。2。3,4,5。6.這個鏈表的倒數第3個結點是值為4的結點 */ package swordForOffer; import utils.ListNode; /** * @author JInShuangQi * * 2015年8月1日 */ public class E15KthNodeFromEnd { public ListNode FindKthToTail(ListNode head,int k){ if(head == null || k <= 0){ return null; } ListNode ANode = head; ListNode BNode = null; for(int i = 0;i<k-1;i++){ if(ANode.next != null) ANode = ANode.next; else return null; } BNode = head; while(ANode.next != null){ ANode = ANode.next; BNode = BNode.next; } return BNode; } public static void main(String[] args){ ListNode head = new ListNode(); ListNode second = new ListNode(); ListNode third = new ListNode(); ListNode forth = new ListNode(); head.next = second; second.next = third; third.next = forth; head.data = 1; second.data = 2; third.data = 3; forth.data = 4; E15KthNodeFromEnd test = new E15KthNodeFromEnd(); ListNode result = test.FindKthToTail(head, -1); System.out.println(result); } }
特殊輸入測試:鏈表的頭結點為Null指針,鏈表的頭結點綜述少於k。k等於0
劍指Offer面試題15(Java版):鏈表中倒數第K個結點