題目
- 判斷單鏈表是否帶環?若帶環,求環的長度?求環的入口點?並計算每個算法的時間複雜度&空間複雜度。
- 判斷兩個鏈表是否相交,若相交,求交點。(假設鏈表不帶環)
- 判斷兩個鏈表是否相交,若相交,求交點。(假設鏈表可能帶環)【升級版】
求帶環鏈表環長
(無環返回 -1)
思路
- 快慢指針法,快指針一次走兩步,慢指針一次走一步,如果鏈表帶環,則從慢指針到達入口點開始,由於快慢指針都在環上移動,每次移動快指針都追慢指針一步,慢指針走一圈之內,快指針一定追上慢指針。
- 相遇之後,一次追一步,再次相遇剛好追一圈——環的長度。
寫代碼
- 創建快慢指針等於頭指針,快指針及其下一節點不爲空(忽略慢指針),進入循環,快指針走兩步,慢指針走一步,如果快~等於慢~,break 結束循環。
- 判斷,如果快~不等於慢~或快指針下一節點爲NULL(如果鏈表只有一個節點,快慢指針都沒走,快~等於慢~),無環,返回NULL。
- 此時已確定鏈表帶環,創建計數器 count = 0,循環,計數,快慢指針再次相遇爲止。
- 返回 count。
代碼
int SizeOfRing(ListNode* pList)
{
ListNode* fast = pList;
ListNode* slow = pList;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
break;
}
if (fast != slow || fast->next == NULL)
return -1;
else
{
int count = 0;
do{
fast = fast->next->next;
slow = slow->next;
count++;
} while (fast != slow);
return count;
}
}
求環入口點
(無環返回 NULL)
思路一(快慢指針法)
- 同求環長,快慢指針相遇停止。
- 此時,將路程分爲三段,一是 T——尾長(非環長度),二是 S——慢指針入環到被追上,三是 C——快指針追慢指針多跑的圈。那麼,慢指針走的路程爲 T+S,快指針走的路程爲 T+S+C,可得 2(T+S)=T+S+C,T=C-S,此時,一個指針從起點出發走 T步,一個指針從快慢指針相遇點出發走 C-S 步,此時兩指針剛好都在入口點。
- 兩指針一個從頭結點出發,一個從快慢指針相遇點出發,一步一步走,相遇的節點即爲入口點(走的步數即爲尾長 T(C-S))。
寫代碼
- 判斷是否帶環並處理不帶環情況,同求環長。
- 兩個指針,一個從頭結點出發,一個從快慢指針相遇點出發,循環各走一步,相遇爲止,返回任意一個指針。
代碼
ListNode* IntranceOfRing(ListNode* pList)
{
ListNode* fast = pList;
ListNode* slow = pList;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
break;
}
if (fast != slow || fast->next == NULL)
return NULL;
while (pList != slow)
{
pList = pList->next;
slow = slow->next;
}
return pList;
}
思路二(轉化無環相交鏈表法)
(斷開完了記得接上)
1. 求快慢指針相遇點。
2. 保存相遇點下一節點 next,相遇點下一節點置空(斷環)。
3. 用原頭結點和 next 調用求無環鏈表相交節點函數(intersect1(),見下題),保存爲 ret。
4. 接環(相遇點下一節點賦 next),返回 ret。
相交鏈表求交點(無環)intersect1()
(無交點返回 NULL)
寫代碼
- 若無環鏈表相交,則尾節點相同(長這樣 >— ),新建指針遍歷兩鏈表,並統計步數(求交點用)。
- 尾節點不同,返回 NULL。
- 比較兩鏈表長度,快指針從長鏈表頭結點開始,走比短鏈表長的節點數。
- 慢指針從另一鏈表頭結點開始,與快指針一起一步一步走,指針相同時停止。返回任意一個指針。
代碼
ListNode* intersect1(ListNode* pList1, ListNode* pList2)
{
int count1 = 0, count2 = 0;
ListNode* p1 = pList1, *p2 = pList2;
while (p1->next)
{
count1++;
p1 = p1->next;
}
while (p2->next)
{
count2++;
p2 = p2->next;
}
if (p1 != p2)return NULL;
if (count1 >= count2)
{
count1 -= count2;
while (count1--)
pList1 = pList1->next;
while (pList1 != pList2)
{
pList1 = pList1->next;
pList2 = pList2->next;
}
return pList1;
}
else
{
count2 -= count1;
while (count2--)
pList2 = pList2->next;
while (pList1 != pList2)
{
pList1 = pList1->next;
pList2 = pList2->next;
}
return pList1;
}
}
相交鏈表求交點(可能帶環)intersect2
(無交點返回 NULL, 環上相交返回第一個鏈表的環入口點)
思路
- 先列舉情況
- 求交點之前,需要先判斷是否帶環,用環入口點函數判斷,並保存入口點。
- 兩個入口點都爲空,是 1、2 兩種情況,直接使用無環鏈表求交點函數。
- 如果只有一個爲空,一定是第三種情況,不相交,返回 NULL。
- 剩下便是兩入口點都不爲空,且入口點相同,則是 3、4 兩種情況,不必做區分,統計從兩頭結點到入口點的長度,然後(同無環鏈表求交點3、4) 比較兩鏈表到入口點長度,快指針從長鏈表頭結點開始,走比短鏈表長的節點數。慢指針從另一鏈表頭結點開始,與快指針一起一步一步走,指針相同時停止。返回任意一個指針。
- 區分最後兩種情況通過一個指針從其中一個入口點出發,走一圈之內,如果與另一入口點相遇,是第 7 種情況,返回第一個鏈表的入口點(也可以是第二個),否則是第 6 種情況,返回 NULL。
代碼
ListNode* intersect2(ListNode* pList1, ListNode* pList2)
{
ListNode* intr1 = IntranceOfRing(pList1);
ListNode* intr2 = IntranceOfRing(pList2);
if (intr1 == NULL && intr2 == NULL)
return intersect1(pList1, pList2);
else if (intr1 == NULL || intr2 == NULL)
return NULL;
else if (intr1 == intr2)
{
int count1 = 0, count2 = 0;
ListNode* p1 = pList1, *p2 = pList2;
while (p1->next != intr1)
{
count1++;
p1 = p1->next;
}
while (p2->next != intr1)
{
count2++;
p2 = p2->next;
}
if (count1 >= count2)
{
count1 -= count2;
while (count1--)
pList1 = pList1->next;
while (pList1 != pList2)
{
pList1 = pList1->next;
pList2 = pList2->next;
}
return pList1;
}
else
{
count2 -= count1;
while (count2--)
pList2 = pList2->next;
while (pList1 != pList2)
{
pList1 = pList1->next;
pList2 = pList2->next;
}
return pList1;
}
}
else
{
ListNode* pList = intr2->next;
while (pList != intr2 && pList != intr1)
{
pList = pList->next;
}
return pList == intr1 ? intr1 : NULL;
}
}