1. 程式人生 > >查詢單鏈表的倒數第k個節點,要求只能遍歷一次連結串列

查詢單鏈表的倒數第k個節點,要求只能遍歷一次連結串列

為了得到倒數第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個結點。這種思路只需要遍歷連結串列一次。對於很長的連結串列,只需要把每個結點從硬碟匯入到記憶體一次。因此這一方法的時間效率前面的方法要高。

struct ListNode
{
    int _value;
    ListNode*_next;
    ListNode(int value = 0, ListNode*pnext = NULL)
        :_value(value)
        , _next(pnext)
    {}
};


ListNode* FindK(ListNode*phead, const int k)//只能遍歷一次
{
    ListNode*first = phead;
    ListNode*second = phead;
    int temp = k;
    while
(temp--) { first = first->_next; } while (first) { first = first->_next; second = second->_next; } return second; } void TestFindk() { ListNode p9(9, NULL); ListNode p7{ 7, &p9 }; ListNode p5{ 5, &p7 }; ListNode p3{ 3
, &p5 }; ListNode p1{ 1, &p3 }; ListNode *head = &p1; FindK(head,2); }