LeetCode 相交链表

编写一个程序,找到两个单链表相交的起始节点。

 

例如,下面的两个链表:

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

我们看到这种方法貌似没有方法一高效::

 

posted @ 2018-09-08 12:08  卉卉卉大爷  阅读(5646)  评论(0编辑  收藏  举报