關於連結串列演算法題的雙指標
經常能夠碰到連結串列的題,當用一個指標遍歷來解決問題的時候,不是無法解決就是效率不佳,典型的就是需要多次遍歷且需要額外的儲存空間。在這種情況下,可以嘗試用兩個指標來遍歷連結串列,而兩指標遍歷連結串列又可以分為兩種情況:1、讓其中一個指標遍歷快一點,比如一次在連結串列中走上兩步;2、讓其中一個指標現在連結串列中走上若干步。
這裡舉三個連結串列相關的題目。
1、 判定連結串列中是否環
第一種方法:可以對連結串列的元素進行標記,如果在預見NULL節點之前再次碰見已標記節點就存在環。缺點:需要改變節點內容,而節點一般是隻讀的。
第二種方法:訪問每一個元素將其儲存在陣列中,這個時候儲存的元素是什麼,如果連結串列中的元素有重複的值,難道儲存節點地址?並且開闢了O(n)的額外空間,如果記憶體不夠呢?
第三種方法:如果假定連結串列存在環,那麼環在前N個元素之中,此時可以設定一個指標指向連結串列的頭部,然後遍歷後N-1個元素,看是否是指標所指元素,如果都不同,指標指向後一個元素,然後遍歷後N-2個元素。缺點:這個演算法複雜度為O(n^2),而且建立在一個前提條件之下。
最優的答案:設定兩個指標,開始都指向連結串列頭,然後其中一個指標每次向前走一步,另一個指標每次向前走兩步,如果快的遇到NULL了,證明該連結串列中沒有環,如果有環,快的指標每次都要比慢的多走一步,最終兩個指標會相遇,(注意:這裡快指標不會跳過慢指標而不相遇,因為它每次都只比慢指標多走一個單位)
bool judge(list *head) { if(head == NULL) { return false;//沒有環 } list *pFast = head; list *pSlow = head; while(pFast->next != NULL && pFast->next->next != NULL) { pFast = pFast->next->next; pSlow = pSlow->next; if(pFast == pSlow) { return true; } } return false; }
2、找到連結串列的中間節點
受上一題的啟發可以運用兩個速度不同的指標來解決,快指標每次走兩步,慢指標每次走一步,這樣當快指標到達連結串列尾部的時候,慢指標就指向了連結串列的中間節點。
3、 輸出連結串列中倒數第K個數
第一種方法:單個指標遍歷兩次,首先遍歷一次連結串列統計總元素個數N,那麼所要找的倒數第K個元素即為第N-K+1個元素。
第二種方法:雙指標遍歷一次,首先前指標先向前走K-1步,即初始的時候前指標指向第K個元素,然後後指標指向第一個元素,然後同步向後單步走,當後指標指向NULL的時候,前指標指向倒數第K個元素。
//注意程式魯棒性,輸入引數檢查,元素個數不足檢查。 ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) { if(pListHead == NULL || k == 0) return NULL; //考慮引數異常 ListNode *pAhead = pListHead; ListNode *pBehind = NULL; for(unsigned int i = 0; i < k - 1; ++ i) { if(pAhead->m_pNext != NULL) pAhead = pAhead->m_pNext; else //要考慮到連結串列的元素不足K個的情況 { return NULL; } } pBehind = pListHead; while(pAhead->m_pNext != NULL) { pAhead = pAhead->m_pNext; pBehind = pBehind->m_pNext; } return pBehind; }
4、 兩連結串列的第一個公共結點——輸入兩個連結串列,找出它們的第一個公共結點。
unsigned int GetListLength(ListNode* pHead)
{
unsigned int nLength = 0;
ListNode* pNode = pHead;
while(pNode != NULL)
{
++ nLength;
pNode = pNode->m_pNext;
}
return nLength;
}
ListNode* FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2)
{
// 得到兩個連結串列的長度
unsigned int nLength1 = GetListLength(pHead1);
unsigned int nLength2 = GetListLength(pHead2);
int nLengthDif = nLength1 - nLength2;
ListNode* pListHeadLong = pHead1;
ListNode* pListHeadShort = pHead2;
if(nLength2 > nLength1)
{
pListHeadLong = pHead2;
pListHeadShort = pHead1;
nLengthDif = nLength2 - nLength1;
}
// 先在長連結串列上走幾步,再同時在兩個連結串列上遍歷
for(int i = 0; i < nLengthDif; ++ i)
pListHeadLong = pListHeadLong->m_pNext;
while((pListHeadLong != NULL) &&
(pListHeadShort != NULL) &&
(pListHeadLong != pListHeadShort))
{
pListHeadLong = pListHeadLong->m_pNext;
pListHeadShort = pListHeadShort->m_pNext;
}
// 得到第一個公共結點
ListNode* pFisrtCommonNode = pListHeadLong;
return pFisrtCommonNode;
}
順便說一句,單鏈表翻轉的演算法運用了3指標,這是為了記錄前後節點。