題目:輸入一個單向鏈表,輸出該鏈表中倒數第k個結點。鏈表的倒數第0個結點爲鏈表的尾指針。
鏈表結點定義如下: struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
分析:爲了得到倒數第k個結點,很自然的想法是先走到鏈表的尾端,再從尾端回溯k步。可是輸入的是單向鏈表,
只有從前往後的指針而沒有從後往前的指針。因此我們需要打開我們的思路。
既然不能從尾結點開始遍歷這個鏈表,我們還是把思路回到頭結點上來。假設整個鏈表有n個結點,那麼倒數第k
個結點是從頭結點開始的第n-k-1個結點(從0開始計數)。如果我們能夠得到鏈表中結點的個數n,那我們只要從
頭結點開始往後走n-k-1步就可以了。如何得到結點數n?這個不難,只需要從頭開始遍歷鏈表,每經過一個結點,
計數器加一就行了。
這種思路的時間複雜度是O(n),但需要遍歷鏈表兩次。第一次得到鏈表中結點個數n,第二次得到從頭結點開始的
第n-k-1個結點即倒數第k個結點。
如果鏈表的結點數不多,這是一種很好的方法。但如果輸入的鏈表的結點個數很多,有可能不能一次性把整個鏈表
都從硬盤讀入物理內存,那麼遍歷兩遍意味着一個結點需要兩次從硬盤讀入到物理內存。我們知道把數據從硬盤讀
入到內存是非常耗時間的操作。我們能不能把鏈表遍歷的次數減少到1?如果可以,將能有效地提高代碼執行的時間
效率。
如果我們在遍歷時維持兩個指針,第一個指針從鏈表的頭指針開始遍歷,在第k-1步之前,第二個指針保持不動;在
第k-1步開始,第二個指針也開始從鏈表的頭指針開始遍歷。由於兩個指針的距離保持在k-1,當第一個(走在前面的)
指針到達鏈表的尾結點時,第二個指針(走在後面的)指針正好是倒數第k個結點。
這種思路只需要遍歷鏈表一次。對於很長的鏈表,只需要把每個結點從硬盤導入到內存一次。因此這一方法的時間效率
前面的方法要高。
#include<iostream>
using namespace std;
struct ListNode
{
int data;
ListNode *next;
};
ListNode *p=NULL,*q=NULL;
ListNode * find(ListNode *head,int k)
{
if(head==NULL)
return NULL;
p=q=head;
for(int i=1;i<=k;i++)
{
if(p->next!=NULL)
p=p->next;
else
return NULL;
}
while(p!=NULL)
{
p=p->next;
q=q->next;
}
return q;
}
ListNode * insert(ListNode * & head,int i)
{
if(head==NULL)
{
ListNode *q=new ListNode();
q->data=i;
q->next=NULL;
head=q;
}
else
{
ListNode *q=new ListNode();
q->data=i;
q->next=head;
head=q;
}
return head;
}
int main()
{
ListNode *head=NULL;
insert(head,1);
insert(head,2);
insert(head,3);
insert(head,4);
insert(head,5);
insert(head,6);
insert(head,7);
insert(head,8);
insert(head,9);
insert(head,10);
cout<<find(head,2)->data<<endl;
}