劍指offer:連結串列中倒數第k個節點
題目描述
輸入一個連結串列,輸出該連結串列中倒數第k個結點。
首先想到的是從頭結點開始遍歷到連結串列的末尾,然後往前回溯k個節點,但是回溯? 這種方式只適用於雙向連結串列,對於單向連結串列,是不可行的。
另外,如果我們已知了連結串列的長度,為n,那麼倒數第k個節點,也就是從前往後的第n-k+1個節點,我們從前往後遍歷n-k+1即可,然而我們並不知道連結串列的長度,所以需要先遍歷一遍連結串列,才能取得它的長度,再遍歷一邊連結串列,才能輸出第k個節點。所以這種方法的時間複雜度較高,需要遍歷兩遍才可以輸出。
實現1:
public ListNode FindKthToTail(ListNode head,int k) { ListNode p=head; if(head==null||k==0){ return null; } int length=1; while(head.next!=null){ head=head.next; length++; } if(k>length){ return null; } for (int i = 1; i <length-k+1; i++) { if(p.next!=null){ p=p.next; } else{ return null; } } return p; }
實現2:用堆疊的方式去實現
public ListNode FindKthToTail(ListNode head,int k) { ListNode result=null; if(head==null||k==0){ return null; } Stack<ListNode> s=new Stack<>(); while(head!=null){ s.push(head); head=head.next; } for (int i =1; i <= k; i++) { if(!s.isEmpty()){ result=s.pop(); }else{ result=null; } } return result; }
上面的這兩種實現時間複雜度太高,並不是我們期望的結果。
如果想要只遍歷一遍連結串列,就輸出倒數的第k個元素,那麼我們就需要兩個指標。
(1)開始,讓指標p1,p2都指向頭結點,指標2先不動,指標1向前走k-1步;此時p1和p2之間相差k-1;
(2)從第k步開始,p1和p2同時移動,當p1指向連結串列的末尾時,此時p1所指的元素就是倒數第k個元素。
初步實現:
public ListNode FindKthToTail2(ListNode head,int k) { ListNode p1=head; ListNode p2=head; //第一個指標先走,直到遍歷到連結串列的第k-1個節點 for (int i = 1; i <=k-1; i++) { p1=p1.next; } //此時p1和p2之間相差k-1個元素,現在兩個指標同時遍歷 while(p1.next!=null){ p1=p1.next; p2=p2.next; } return p2;
}
這個實現是有問題的,會報空指標異常,因為程式碼中存在多種特殊的情況沒有被考慮到。
首先,如果給了一個空連結串列,那它的next是不存在的,此時會報錯,
其次,如果給的k=0,則k-1=-1,也會報錯;
另外,需要返回的k大於所給的連結串列的總長度時,也會報錯,
考慮到以上情況,對程式碼做一些安全性的考慮,實現如下:
實現3:
public ListNode FindKthToTail(ListNode head,int k) {
if(head==null||k==0){
return null;
}
ListNode p1=head;
ListNode p2=head;
//第一個指標先走,直到遍歷到連結串列的第k-1個節點
for (int i = 1; i <=k-1; i++) {
if(p1.next!=null){
p1=p1.next;
}
else{
return null;
}
}
//此時p1和p2之間相差k-1個元素,現在兩個指標同時遍歷
while(p1.next!=null){
p1=p1.next;
p2=p2.next;
}
return p2;
參考: