查詢單鏈表的倒數第k個節點,要求只能遍歷一次連結串列
阿新 • • 發佈:2019-01-02
為了得到倒數第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);
}