編寫一個程序,找到兩個單鏈表相交的起始節點。
例如,下面的兩個鏈表:
A: a1 → a2
↘
c1 → c2 → c3
↗
B: b1 → b2 → b3
在節點 c1 開始相交。
注意:
- 如果兩個鏈表沒有交點,返回
null
. - 在返回結果后,兩個鏈表仍須保持原有的結構。
- 可假定整個鏈表結構中沒有循環。
- 程序盡量滿足 O(n) 時間復雜度,且僅用 O(1) 內存。
方法一:
1 /* C++ */
2 /** 3 * Definition for singly-linked list. 4 * struct ListNode { 5 * int val; 6 * ListNode *next; 7 * ListNode(int x) : val(x), next(NULL) {} 8 * }; 9 */
10 class Solution { 11 public: 12 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { 13 if(headA == NULL || headB == NULL){ 14 return NULL; 15 } 16 ListNode *a = headA; 17 ListNode *b = headB; 18 // 計算A和B的長度
19 int lenA = 0,lenB = 0; 20 while(a->next != NULL){ 21 a = a->next; 22 lenA++; 23 } 24 while(b->next != NULL){ 25 b = b->next; 26 lenB++; 27 } 28 int step = lenB - lenA; 29 if(step>0){ 30 //說明B長
31 a = headB; 32 b = headA; 33 }else{// 說明A長
34 a = headA; 35 b = headB; 36 step *= -1; 37 } 38 while(step -- ){ 39 a = a->next; 40 } 41 while(a != b){ 42 a = a->next;b = b->next; 43 } 44 return a; 45 } 46 };
python:
1 # Definition for singly-linked list.
2 # class ListNode(object):
3 # def __init__(self, x):
4 # self.val = x
5 # self.next = None
6
7 class Solution(object): 8 def getIntersectionNode(self, headA, headB): 9 """
10 :type head1, head1: ListNode 11 :rtype: ListNode 12 """
13 if headA==None or headB==None: 14 return None 15 # 計算A和B的長度
16 lenA, lenB = 0, 0 17 a, b = headA, headB 18 while a.next != None: 19 a = a.next 20 lenA += 1
21 while b.next != None: 22 b = b.next 23 lenB += 1
24 if lenA > lenB: 25 step, a, b = lenA - lenB, headA, headB 26 else: 27 step, a, b = lenB - lenA, headB, headA 28
29 while step: 30 step -= 1
31 a = a.next 32
33 while a != b: 34 a, b = a.next, b.next 35 return a
方法二:
原理來自:如何判斷單鏈表是否有環、環的入口、環的長度和總長 - CSDN博客
1)首先判斷鏈表是否有環
要想判斷有環,我們可以聯系實際生活中的例子,很容易就想到操場上跑圈,因為是環形,所以快的肯定會追上慢的,所以我們可以應用到鏈表上,用一個快指針和一個慢指針,但是細想一下發現,我們在跑操的時候相遇時坐標位置不一定是整數啊(這里相比鏈表節點而言的),而鏈表是一個節點連接起來,我們怎么做,能讓他們在節點上相遇呢,這里就要為2個指針找到合適的速度,使之能夠恰巧在某一結點上相遇。
原理:如果快的指針走到NULL,說明無環;而fast==slow相遇,則證明肯定存在環。
公式推導
為什么存在環的情況下,兩個指針會相遇呢?以下推到n都是指 環長!
1.假定2個指針同一個起點
們讓兩個指針全部指向頭節點,然后給slow指針的速度為一步,而fast指針的速度為M步,則在第i次迭代的時候,slow指針走到i mod n,而fast指向Mi mod n,要想slow和fast相遇,則i mod n=Mi mod n,(M-1)i mod n,則我們可以令M=2(最小的可以取得值),i mod n = 0,則 i=n時,相遇,所以我們可以給fast 2倍的速度,這樣它們會在 最后一個節點相遇。
2.假定不在同一個起點,並且fast提前K位置
其實這個類似鏈表中含有個小環的情況,即不是所有點在環中的情況,這樣當slow即將進入環狀的時候,fast已經在環中k mod n位置了,所以問題轉化為假定不在同一個起點,並且fast提前K位置,是否會在一點相遇?
fast的速度仍設置為2倍,假定第i次迭代時,slow指向i mod n,fast指向k+2i mod n,其k大於0小於你,那么i ≡ (2i+k)(mod n) -> (i+k) mod n = 0 -> 當i=n-k時,p與q相遇。
變相理解,如何同一個起點出發,他們會在整圈(也就是最后一個節點)相遇,現在fast在提前K位置出發,這樣就會使相遇點本來是最后節點,現在fast少走k步,即可與slow相遇,所以在n-K位置相遇。類似問題求倒數第K個節點:http://blog.csdn.net/dawn_after_dark/article/details/73611115
所以不管是圖1的鏈表,還是圖2的鏈表,只要有環,快指針跟慢指針相遇,逆命題也成立;所有當快指針跟慢指針相遇,就一定存在環。
2)找到兩鏈表的交點
我們已經在上面的討論中,已經得知slow與fast會在環中n-k位置相遇,我們先靠主觀方面來探討這個問題,兩個指針同時從頭節點開始走,當慢指針即將進入環中的時候,快指針位於k mod n,說明慢指針走的這段路程也能對應k mod n, 因為快指針是慢指針速度的2倍,所以快指針在環中走的距離與慢指針走的距離一樣。而我們發現相遇點位於n-k,再走k步就可以到達環的入口,並且慢指針走的路程也能對應k mod n,所以我們再令取2指針,一個指向頭節點,另一個指向碰撞點,都以1步的速度前進,這兩個指針相遇點就是環的入口,這個結論適用於全環的鏈表,因為這時k=0,頭節點走一步就到了環的入口了。
以上只是我們主觀的理解方式,如果采用推導呢,slow走過的路程為s,環長為n,所以,2s=s+k+(m-1)n,化簡為s=k+(m-1)n,所以slow在環外相當於走了k+(m-1)n。
而碰撞點位於n-k的位置,所以要想走到環入點,則需要走k+mn步,這時你就會發現只要讓這兩個指針從頭節點與碰撞點以一步的速度過來,就一定會在環入點相遇,從而求出環入點!
簡言之:
若存在環:
slow指針速度為1,fast指針速度為2。當slow到達環入口點時,fast已經在環內走了k步,這時候slow總共走了k步,fast總共走了2*k步,也就是環外的鏈表的長度為k。若slow又走了i步,與fast相遇(此時fast又走了2*i步),這時候有
i ≡ (2i+k)(mod n)
即:(i+k) mod n = 0
即:當i=n-k時,p與q相遇
所以這時候再從相遇點走k步即到達環的入口點。所以我們可以使用一個新指針new從環外鏈表的頭開始走,同時slow繼續走,直到相遇,相遇點即為環的入口點。
1 # Definition for singly-linked list.
2 # class ListNode(object):
3 # def __init__(self, x):
4 # self.val = x
5 # self.next = None
6
7 class Solution(object): 8 def getIntersectionNode(self, headA, headB): 9 """
10 :type head1, head1: ListNode 11 :rtype: ListNode 12 """
13 if headA == None or headB == None: 14 return None 15
16 # 檢查是否有交點, 如果沒有交點,返回None
17 a, b = headA, headB 18 while a.next!=None: 19 a = a.next 20 while b.next!=None: 21 b = b.next 22 if a.val != b.val: 23 return None 24
25 # 接下來找出交點,先將鏈表B的首尾相連
26 b = headB 27 while b.next != None: 28 b = b.next 29 b.next = headB 30
31 a = headA.next #慢指針
32 c = headA.next.next #快指針
33 while a.val != c.val: # 找相遇點
34 a, c = a.next, c.next.next 35
36 c = headA 37 while a.val != c.val: 38 a, c = a.next, c.next 39
40 # 找到相交點之后,需要將兩鏈表復原
41 b.next = None 42
43 return c
我們看到這種方法貌似沒有方法一高效::