1. 程式人生 > >程式設計師面試一百題-09-查詢單向連結串列中倒數第k個結點

程式設計師面試一百題-09-查詢單向連結串列中倒數第k個結點

1-題目 :
輸入一個單向連結串列,輸出該連結串列中倒數第k個結點,連結串列的倒數第0個結點為連結串列的尾指標。

2-思路 :
2.1-錯誤思路 : 為了得到倒數第k個結點,很自然的想法是先走到連結串列的尾端,再從尾端回溯k步,可是單向連結串列只有從前往後的指標而沒有從後往前的指標
2.2-正確思路1 : 遍歷連結串列兩次。第一次得到連結串列中結點個數n,第二次得到從頭結點開始的第n-k-1個結點即倒數第k個結點(從0開始計數)。
2.3-正確思路2 : 如果結點個數很多,可能不能一次性把整個連結串列都從硬碟讀入記憶體,那麼遍歷兩遍意味著一個結點需要兩次從硬碟讀入到記憶體,這是非常耗時間的操作。能不能把連結串列遍歷的次數減少到1?

在遍歷時維持兩個指標,第一個指標從連結串列的頭指標開始遍歷,在第k-1步之前,第二個指標保持不動;在第k-1步開始,第二個指標也開始從連結串列的頭指標開始遍歷。由於兩個指標的距離保持在k-1,當第一個(走在前面的)指標到達連結串列的尾結點時,第二個指標(走在後面的)指標正好是倒數第k個結點。這種思路只需要遍歷連結串列一次,對於很長的連結串列,只需要把每個結點從硬碟匯入到記憶體一次。

3-程式碼(維持兩個指標) :

//連結串列結點的定義
struct ListNode
{
    int m_nKey;
    ListNode *m_pNext;
};

ListNode *FindKthToTail(ListNode *pListHead, unsigned int k)
{
    //若連結串列為空
    if (pListHead == NULL)
    {
        return NULL;
    }
    ListNode *pAhead = pListHead;
    ListNode *pBehind = NULL;

    //k步之前,只移動前指標
    for (unsigned int i = 0; i < k; i++)
    {
        if (pAhead->m_pNext != NULL)
        {
            pAhead = pAhead->m_pNext;
        }
        else
        {
            return NULL;
        }
    }
    //k步之後,前後指標同時移動
    pBehind = pListHead;
    while (pAhead->m_pNext != NULL)
    {
        pAhead = pAhead->m_pNext;
        pBehind = pBehind->m_pNext;
    }

    //返回後指標即為倒數第k個結點
    return pBehind;
}