題目:輸入一個單向鏈表,輸出該鏈表中倒數第k個結點。鏈表的倒數第0個結點為鏈表的尾指針。
分析:為了得到倒數第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> #include <map> using namespace std; typedef struct ListNode{ int m_nkey; ListNode *m_pnext; } ListNode,*Node; int main() { int n,k; cout<<"The length of the list: "<<endl; cin>>n; cout<<"Which one to select(from tail): "<<endl; cin>>k; Node head,tmp,last; for(int i=n;i>=1;i--) { tmp=new ListNode(); tmp->m_nkey=i; if(i==n) head=tmp; else last->m_pnext=tmp; tmp->m_pnext=NULL; last=tmp; } Node p=head; Node q=head; int i=1; while(p!=NULL) { if(i<=k) { i++; p=p->m_pnext; } else { p=p->m_pnext; q=q->m_pnext; } } cout<<"The data is: "<<q->m_nkey<<endl; }