輕鬆搞定面試中的連結串列題目
版權所有,轉載請註明出處,謝謝!
http://blog.csdn.net/walkinginthewind/article/details/7393134
連結串列是最基本的資料結構,面試官也常常用連結串列來考察面試者的基本能力,而且連結串列相關的操作相對而言比較簡單,也適合考察寫程式碼的能力。連結串列的操作也離不開指標,指標又很容易導致出錯。綜合多方面的原因,連結串列題目在面試中佔據著很重要的地位。本文對連結串列相關的面試題做了較為全面的整理,希望能對找工作的同學有所幫助。
連結串列結點宣告如下:
struct ListNode
{
int m_nKey;
ListNode * m_pNext;
};
題目列表:
1. 求單連結串列中結點的個數
2. 將單連結串列反轉
3. 查詢單連結串列中的倒數第K個結點(k > 0)
4. 查詢單連結串列的中間結點
5. 從尾到頭列印單連結串列
6. 已知兩個單連結串列pHead1 和pHead2 各自有序,把它們合併成一個連結串列依然有序
7. 判斷一個單連結串列中是否有環
8. 判斷兩個單連結串列是否相交
9. 求兩個單連結串列相交的第一個節點
10. 已知一個單連結串列中存在環,求進入環中的第一個節點
11. 給出一單連結串列頭指標pHead和一節點指標pToBeDeleted,O(1)時間複雜度刪除節點pToBeDeleted
詳細解答
這是最最基本的了,應該能夠迅速寫出正確的程式碼,注意檢查連結串列是否為空。時間複雜度為O(n)。參考程式碼如下:
- // 求單連結串列中結點的個數
- unsigned int GetListLength(ListNode * pHead)
- {
- if(pHead == NULL)
- return 0;
- unsigned int nLength = 0;
- ListNode * pCurrent = pHead;
- while(pCurrent != NULL)
- {
- nLength++;
- pCurrent = pCurrent->m_pNext;
- }
- return nLength;
- }
從頭到尾遍歷原連結串列,每遍歷一個結點,將其摘下放在新連結串列的最前端。注意連結串列為空和只有一個結點的情況。時間複雜度為O(n)。參考程式碼如下:
- // 反轉單連結串列
- ListNode * ReverseList(ListNode * pHead)
- {
- // 如果連結串列為空或只有一個結點,無需反轉,直接返回原連結串列頭指標
- if(pHead == NULL || pHead->m_pNext == NULL)
- return pHead;
- ListNode * pReversedHead = NULL; // 反轉後的新連結串列頭指標,初始為NULL
- ListNode * pCurrent = pHead;
- while(pCurrent != NULL)
- {
- ListNode * pTemp = pCurrent;
- pCurrent = pCurrent->m_pNext;
- pTemp->m_pNext = pReversedHead; // 將當前結點摘下,插入新連結串列的最前端
- pReversedHead = pTemp;
- }
- return pReversedHead;
- }
3. 查詢單連結串列中的倒數第K個結點(k > 0)
最普遍的方法是,先統計單連結串列中結點的個數,然後再找到第(n-k)個結點。注意連結串列為空,k為0,k為1,k大於連結串列中節點個數時的情況。時間複雜度為O(n)。程式碼略。
這裡主要講一下另一個思路,這種思路在其他題目中也會有應用。
主要思路就是使用兩個指標,先讓前面的指標走到正向第k個結點,這樣前後兩個指標的距離差是k-1,之後前後兩個指標一起向前走,前面的指標走到最後一個結點時,後面指標所指結點就是倒數第k個結點。
參考程式碼如下:
- // 查詢單連結串列中倒數第K個結點
- ListNode * RGetKthNode(ListNode * pHead, unsigned int k) // 函式名前面的R代表反向
- {
- if(k == 0 || pHead == NULL) // 這裡k的計數是從1開始的,若k為0或連結串列為空返回NULL
- return NULL;
- ListNode * pAhead = pHead;
- ListNode * pBehind = pHead;
- while(k > 1 && pAhead != NULL) // 前面的指標先走到正向第k個結點
- {
- pAhead = pAhead->m_pNext;
- k--;
- }
- if(k > 1 || pAhead == NULL) // 結點個數小於k,返回NULL
- return NULL;
- while(pAhead->m_pNext != NULL) // 前後兩個指標一起向前走,直到前面的指標指向最後一個結點
- {
- pBehind = pBehind->m_pNext;
- pAhead = pAhead->m_pNext;
- }
- return pBehind; // 後面的指標所指結點就是倒數第k個結點
- }
4. 查詢單連結串列的中間結點
此題可應用於上一題類似的思想。也是設定兩個指標,只不過這裡是,兩個指標同時向前走,前面的指標每次走兩步,後面的指標每次走一步,前面的指標走到最後一個結點時,後面的指標所指結點就是中間結點,即第(n/2+1)個結點。注意連結串列為空,連結串列結點個數為1和2的情況。時間複雜度O(n)。參考程式碼如下:
- // 獲取單連結串列中間結點,若連結串列長度為n(n>0),則返回第n/2+1個結點
- ListNode * GetMiddleNode(ListNode * pHead)
- {
- if(pHead == NULL || pHead->m_pNext == NULL) // 連結串列為空或只有一個結點,返回頭指標
- return pHead;
- ListNode * pAhead = pHead;
- ListNode * pBehind = pHead;
- while(pAhead->m_pNext != NULL) // 前面指標每次走兩步,直到指向最後一個結點,後面指標每次走一步
- {
- pAhead = pAhead->m_pNext;
- pBehind = pBehind->m_pNext;
- if(pAhead->m_pNext != NULL)
- pAhead = pAhead->m_pNext;
- }
- return pBehind; // 後面的指標所指結點即為中間結點
- }
對於這種顛倒順序的問題,我們應該就會想到棧,後進先出。所以,這一題要麼自己使用棧,要麼讓系統使用棧,也就是遞迴。注意連結串列為空的情況。時間複雜度為O(n)。參考程式碼如下:
自己使用棧:
- // 從尾到頭列印連結串列,使用棧
- void RPrintList(ListNode * pHead)
- {
- std::stack<ListNode *> s;
- ListNode * pNode = pHead;
- while(pNode != NULL)
- {
- s.push(pNode);
- pNode = pNode->m_pNext;
- }
- while(!s.empty())
- {
- pNode = s.top();
- printf("%d\t", pNode->m_nKey);
- s.pop();
- }
- }
使用遞迴函式:
- // 從尾到頭列印連結串列,使用遞迴
- void RPrintList(ListNode * pHead)
- {
- if(pHead == NULL)
- {
- return;
- }
- else
- {
- RPrintList(pHead->m_pNext);
- printf("%d\t", pHead->m_nKey);
- }
- }
6. 已知兩個單連結串列pHead1 和pHead2 各自有序,把它們合併成一個連結串列依然有序
這個類似歸併排序。尤其注意兩個連結串列都為空,和其中一個為空時的情況。只需要O(1)的空間。時間複雜度為O(max(len1, len2))。參考程式碼如下:
- // 合併兩個有序連結串列
- ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2)
- {
- if(pHead1 == NULL)
- return pHead2;
- if(pHead2 == NULL)
- return pHead1;
- ListNode * pHeadMerged = NULL;
- if(pHead1->m_nKey < pHead2->m_nKey)
- {
- pHeadMerged = pHead1;
- pHeadMerged->m_pNext = NULL;
- pHead1 = pHead1->m_pNext;
- }
- else
- {
- pHeadMerged = pHead2;
- pHeadMerged->m_pNext = NULL;
- pHead2 = pHead2->m_pNext;
- }
- ListNode * pTemp = pHeadMerged;
- while(pHead1 != NULL && pHead2 != NULL)
- {
- if(pHead1->m_nKey < pHead2->m_nKey)
- {
- pTemp->m_pNext = pHead1;
- pHead1 = pHead1->m_pNext;
- pTemp = pTemp->m_pNext;
- pTemp->m_pNext = NULL;
- }
- else
- {
- pTemp->m_pNext = pHead2;
- pHead2 = pHead2->m_pNext;
- pTemp = pTemp->m_pNext;
- pTemp->m_pNext = NULL;
- }
- }
- if(pHead1 != NULL)
- pTemp->m_pNext = pHead1;
- else if(pHead2 != NULL)
- pTemp->m_pNext = pHead2;
- return pHeadMerged;
- }
也有如下遞迴解法:
- ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2)
- {
- if(pHead1 == NULL)
- return pHead2;
- if(pHead2 == NULL)
- return pHead1;
- ListNode * pHeadMerged = NULL;
- if(pHead1->m_nKey < pHead2->m_nKey)
- {
- pHeadMerged = pHead1;
- pHeadMerged->m_pNext = MergeSortedList(pHead1->m_pNext, pHead2);
- }
- else
- {
- pHeadMerged = pHead2;
- pHeadMerged->m_pNext = MergeSortedList(pHead1, pHead2->m_pNext);
- }
- return pHeadMerged;
- }
這裡也是用到兩個指標。如果一個連結串列中有環,也就是說用一個指標去遍歷,是永遠走不到頭的。因此,我們可以用兩個指標去遍歷,一個指標一次走兩步,一個指標一次走一步,如果有環,兩個指標肯定會在環中相遇。時間複雜度為O(n)。參考程式碼如下:
- bool HasCircle(ListNode * pHead)
- {
- ListNode * pFast = pHead; // 快指標每次前進兩步
- ListNode * pSlow = pHead; // 慢指標每次前進一步
- while(pFast != NULL && pFast->m_pNext != NULL)
- {
- pFast = pFast->m_pNext->m_pNext;
- pSlow = pSlow->m_pNext;
- if(pSlow == pFast) // 相遇,存在環
- return true;
- }
- return false;
- }
如果兩個連結串列相交於某一節點,那麼在這個相交節點之後的所有節點都是兩個連結串列所共有的。也就是說,如果兩個連結串列相交,那麼最後一個節點肯定是共有的。先遍歷第一個連結串列,記住最後一個節點,然後遍歷第二個連結串列,到最後一個節點時和第一個連結串列的最後一個節點做比較,如果相同,則相交,否則不相交。時間複雜度為O(len1+len2),因為只需要一個額外指標儲存最後一個節點地址,空間複雜度為O(1)。參考程式碼如下:
- bool IsIntersected(ListNode * pHead1, ListNode * pHead2)
- {
- if(pHead1 == NULL || pHead2 == NULL)
- return false;
- ListNode * pTail1 = pHead1;
- while(pTail1->m_pNext != NULL)
- pTail1 = pTail1->m_pNext;
- ListNode * pTail2 = pHead2;
- while(pTail2->m_pNext != NULL)
- pTail2 = pTail2->m_pNext;
- return pTail1 == pTail2;
- }
9. 求兩個單連結串列相交的第一個節點
對第一個連結串列遍歷,計算長度len1,同時儲存最後一個節點的地址。
對第二個連結串列遍歷,計算長度len2,同時檢查最後一個節點是否和第一個連結串列的最後一個節點相同,若不相同,不相交,結束。
兩個連結串列均從頭節點開始,假設len1大於len2,那麼將第一個連結串列先遍歷len1-len2個節點,此時兩個連結串列當前節點到第一個相交節點的距離就相等了,然後一起向後遍歷,知道兩個節點的地址相同。
時間複雜度,O(len1+len2)。參考程式碼如下:
- ListNode* GetFirstCommonNode(ListNode * pHead1, ListNode * pHead2)
- {
- if(pHead1 == NULL || pHead2 == NULL)
- return NULL;
- int len1 = 1;
- ListNode * pTail1 = pHead1;
- while(pTail1->m_pNext != NULL)
- {
- pTail1 = pTail1->m_pNext;
- len1++;
- }
- int len2 = 1;
- ListNode * pTail2 = pHead2;
- while(pTail2->m_pNext != NULL)
- {
- pTail2 = pTail2->m_pNext;
- len2++;
- }
- if(pTail1 != pTail2) // 不相交直接返回NULL
- return NULL;
- ListNode * pNode1 = pHead1;
- ListNode * pNode2 = pHead2;
- // 先對齊兩個連結串列的當前結點,使之到尾節點的距離相等
- if(len1 > len2)
- {
- int k = len1 - len2;
- while(k--)
- pNode1 = pNode1->m_pNext;
- }
- else
- {
- int k = len2 - len1;
- while(k--)
- pNode2 = pNode2->m_pNext;
- }
- while(pNode1 != pNode2)
- {
- pNode1 = pNode1->m_pNext;
- pNode2 = pNode2->m_pNext;
- }
- return pNode1;
- }
10. 已知一個單連結串列中存在環,求進入環中的第一個節點
首先判斷是否存在環,若不存在結束。在環中的一個節點處斷開(當然函式結束時不能破壞原連結串列),這樣就形成了兩個相交的單連結串列,求進入環中的第一個節點也就轉換成了求兩個單連結串列相交的第一個節點。參考程式碼如下:
- ListNode* GetFirstNodeInCircle(ListNode * pHead)
- {
- if(pHead == NULL || pHead->m_pNext == NULL)
- return NULL;
- ListNode * pFast = pHead;
- ListNode * pSlow = pHead;
- while(pFast != NULL && pFast->m_pNext != NULL)
- {
- pSlow = pSlow->m_pNext;
- pFast = pFast->m_pNext->m_pNext;
- if(pSlow == pFast)
- break;
- }
- if(pFast == NULL || pFast->m_pNext == NULL)
- return NULL;
- // 將環中的此節點作為假設的尾節點,將它變成兩個單連結串列相交問題
- ListNode * pAssumedTail = pSlow;
- ListNode * pHead1 = pHead;
- ListNode * pHead2 = pAssumedTail->m_pNext;
- ListNode * pNode1, * pNode2;
- int len1 = 1;
- ListNode * pNode1 = pHead1;
- while(pNode1 != pAssumedTail)
- {
- pNode1 = pNode1->m_pNext;
- len1++;
- }
- int len2 = 1;
- ListNode * pNode2 = pHead2;
- while(pNode2 != pAssumedTail)
- {
- pNode2 = pNode2->m_pNext;
- len2++;
- }
- pNode1 = pHead1;
- pNode2 = pHead2;
- // 先對齊兩個連結串列的當前結點,使之到尾節點的距離相等
- if(len1 > len2)
- {
- int k = len1 - len2;
- while(k--)
- pNode1 = pNode1->m_pNext;
- }
- else
- {
- int k = len2 - len1;
- while(k--)
- pNode2 = pNode2->m_pNext;
- }
- while(pNode1 != pNode2)
- {
- pNode1 = pNode1->m_pNext;
- pNode2 = pNode2->m_pNext;
- }
- return pNode1;
- }
11. 給出一單連結串列頭指標pHead和一節點指標pToBeDeleted,O(1)時間複雜度刪除節點pToBeDeleted
對於刪除節點,我們普通的思路就是讓該節點的前一個節點指向該節點的下一個節點,這種情況需要遍歷找到該節點的前一個節點,時間複雜度為O(n)。對於連結串列,連結串列中的每個節點結構都是一樣的,所以我們可以把該節點的下一個節點的資料複製到該節點,然後刪除下一個節點即可。要注意最後一個節點的情況,這個時候只能用常見的方法來操作,先找到前一個節點,但總體的平均時間複雜度還是O(1)。參考程式碼如下:
- void Delete(ListNode * pHead, ListNode * pToBeDeleted)
- {
- if(pToBeDeleted == NULL)
- return;
- if(pToBeDeleted->m_pNext != NULL)
- {
- pToBeDeleted->m_nKey = pToBeDeleted->m_pNext->m_nKey; // 將下一個節點的資料複製到本節點,然後刪除下一個節點
- ListNode * temp = pToBeDeleted->m_pNext;
- pToBeDeleted->m_pNext = pToBeDeleted->m_pNext->m_pNext;
- delete temp;
- }
- else // 要刪除的是最後一個節點
- {
- if(pHead == pToBeDeleted) // 連結串列中只有一個節點的情況
- {
- pHead = NULL;
- delete pToBeDeleted;
- }
- else
- {
- ListNode * pNode = pHead;
- while(pNode->m_pNext != pToBeDeleted) // 找到倒數第二個節點
- pNode = pNode->m_pNext;
- pNode->m_pNext = NULL;
- delete pToBeDeleted;
- }
- }
- }
相關文章
- 輕鬆搞定面試中的二叉樹題目面試二叉樹
- [演算法總結] 17 題搞定 BAT 面試——連結串列題演算法BAT面試
- 連結串列面試題(四)---查詢連結串列的中間節點面試題
- 連結串列專題——面試中常見的連結串列問題面試
- 連結串列面試題(二)---連結串列逆序(連結串列反轉)面試題
- 【圖解連結串列類面試題】移除連結串列元素圖解面試題
- 【圖解連結串列類面試題】環形連結串列圖解面試題
- 【LeetCode-連結串列】面試題-反轉連結串列LeetCode面試題
- 連結串列面試題(七)---合併兩個有序連結串列面試題
- 連結串列面試題(十一)---求帶環單連結串列 環的入口點面試題
- 連結串列面試題(十)---求帶環單連結串列的環的長度面試題
- (連結串列)連結串列的排序問題排序
- 連結串列面試題(十三)---求兩個都不帶環的連結串列相交的結點面試題
- 連結串列面試題(九)---判斷一個連結串列是否帶環面試題
- 搞懂單連結串列常見面試題面試題
- 連結串列面試題(八)---約瑟夫環面試題
- leetcode題目解析(js)--連結串列LeetCodeJS
- 一文講透連結串列操作,看完你也能輕鬆寫出正確的連結串列程式碼
- 連結串列面試題(十二)---判斷兩個都不帶環的連結串列是否相交面試題
- 一個node連結串列翻轉的面試題面試題
- 面試題35:複雜連結串列的複製面試題
- 【C++】“反轉連結串列”相關的題目C++
- 面試中HashMap連結串列成環的問題你答出了嗎面試HashMap
- 演算法題中的連結串列演算法
- 連結串列面試題(五)---尋找連結串列的倒數第k個結點O(N)面試題
- 連結串列面試題(一)---刪除一個無頭單連結串列的非尾結點面試題
- 連結串列面試題(六)---刪除單連結串列倒數第k個結點面試題
- 面試中必知必會的那些題——第一題 單連結串列倒置面試
- javascript中的連結串列結構—雙向連結串列JavaScript
- 辦公室革命,教你輕鬆搞定輕鬆玩轉ExcelExcel
- 輕鬆搞定分組報表中的各種排序排序
- 劍指offer面試題15 連結串列中倒數第K個結點面試題
- Day4(連結串列)|24. 兩兩交換連結串列中的節點 & 19.刪除連結串列的倒數第N個節點 & 面試題 02.07. 連結串列相交 &142.環形連結串列II面試題
- **24. 兩兩交換連結串列中的節點****19.刪除連結串列的倒數第N個節點****面試題 02.07. 連結串列相交****142.環形連結串列II**面試題
- 面試必備的「反轉連結串列」面試
- 資料結構和演算法面試題系列—連結串列資料結構演算法面試題
- 看圖輕鬆理解資料結構與演算法系列(單向連結串列)資料結構演算法
- 看圖輕鬆理解資料結構與演算法系列(雙向連結串列)資料結構演算法