輸入乙個單向鍊錶,輸出該鍊錶中倒數第k個結點。鍊錶的倒數第0個結點為鍊錶的尾指標。鍊錶結點定義如下: struct listnode
;分析:為了得到倒數第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個結點。
這種思路只需要遍歷鍊錶一次。對於很長的鍊錶,只需要把每個結點從硬碟匯入到記憶體一次。因此這一方法的時間效率前面的方法要高。
思路一的參考**:
listnode* findkthtotail_solution1(listnode* plisthead, unsigned int k)
if(nnum < k)
return null;
pcur = plisthead;
for(unsigned int i = 0; i < nnum - k; ++ i)
pcur = pcur->m_pnext;
return pcur;
}思路二的參考**:
listnode* findkthtotail_solution2(listnode* plisthead, unsigned int k)
}pbehind = plisthead;
while(pahead->m_pnext != null)
return pbehind;
}討論:這道題的**有大量的指標操作。在軟體開發中,錯誤的指標操作是大部分問題的根源。因此每個公司都希望程式設計師在操作指標時有良好的習慣,比如使用指標之前判斷是不是空指標。這些都是程式設計的細節,但如果這些細節把握得不好,很有可能就會和心儀的公司失之交臂。
另外,這兩種思路對應的**都含有迴圈。含有迴圈的**經常出的問題是在迴圈結束條件的判斷。是該用小於還是小於等於?是該用k還是該用k-1?由於題目要求的是從0開始計數,而我們的習慣思維是從1開始計數,因此首先要想好這些邊界條件再開始編寫**,再者要在編寫完**之後再用邊界值、邊界值減1、邊界值加1都執行一次(在紙上寫**就只能在心裡執行了)。
擴充套件:和這道題類似的題目還有:輸入乙個單向鍊錶。如果該鍊錶的結點數為奇數,輸出中間的結點;如果鍊錶結點數為偶數,輸出中間兩個結點前面的乙個。如果各位感興趣,請自己分析並編寫**。
查詢鍊錶中倒數第k個結點
定義兩個指標。第乙個指標從鍊錶的頭指標開始遍歷向前走k 1,第二個指標保持不動 從第k步開始,第二個指標也開始從鍊錶的頭指標開始遍歷。由於兩個指標的距離保持在k 1,當第乙個 走在前面的 指標到達鍊錶的尾結點時,第二個指標 走在後面的 指標正好是倒數第k個結點。includeusing namesp...
查詢鍊錶中倒數第k個結點
include using namespace std typedef struct lnodelnode,linklist void initlist linklist l void createlist r linklist l,int n return void show linklist l...
查詢鍊錶中倒數第k個結點
題目 輸入乙個單向鍊錶,輸出該鍊錶中倒數第k個結點。鍊錶的倒數第0個結點為鍊錶的尾指標。鍊錶結點定義如下 struct listnode 兩個方法,第乙個先遍歷,獲得鍊錶總共的長度,這樣就知道從head到達倒數第k個需要移動多少次,從頭在移動一遍。第二個,兩個指標,第二個比第乙個多移動k次之後,兩個...