如题,这类问题在LeetCode上和剑指offer上总共有这些涉及:
- LeetCode:141,142,160
- 剑指offer:两个链表的第一个公共节点(默认是无环单链表)、链表中环的入口节点
- 补充:两个未知是否有环的单链表第一个公共节点
我直接叙述第三个问题,也就是补充问题。因为问题3包含了:判断链表是否有环(输出环的入口节点)、无环单链表相交的问题,以及两个有环单链表的相交问题、一个有环一个无环单链表的相交问题
以下,正文开始:
提示:无环单链表和有环单链表的相交是不同的问题,所以要先分析链表是否成环
- 第一步,判断单链表是否成环,并返回成环的第一个Node:
- 用dict:直接将单链表的节点挨个存入dict。若遍历到某个节点存在,说明成环,该节点就是成环第一个Node;遍历完后没有任何一个节点存在于这个dict中,说明没有环
- 不用dict:快慢指针。快指针一次2步,慢指针一次1步;快慢指针从链表头结点一起出发,若快指针走到None了,说明单链表肯定没有环;如果快慢指针相遇,则说明快指针将慢指针套圈,也就是说二者是在环上相遇,此时快指针回到链表头结点然后一次1步向后走,再次遇到慢指针时,该点就是成环第一个Node(玄学)
- 第二步:分情况讨论有无环的两个单链表相交问题:
- 先调用上面第一步的函数,传入两个单链表头结点head,得到是否成环以及成环的第一个Node(不成环,返回None)
- 判断两个链表是否有环,并以此下面进行分类讨论:
- 若两单链表都无环:
- 用dict:还是老方法,遍历链表1,将链表1节点都存入dict中。然后遍历链表2,如果遍历到结尾都没发现链表2的Node在dict中存在,则此两者不相交;若第一次遍历到链表2的Node在dict中,这个Node就是二者相交的第一个Node
- 不用dict:分别遍历链表1,2,得到两个链表长度之差delta和两个链表的尾节点。若二者尾节点不相同,则说明二者必然不相交;若尾节点相同,则说明相交。此时让长度较长的链表先走完二者长度的差值这些节点,然后二者一起遍历,直到出现相同Node,这就是相交节点
- 若一链表有环,一链表无环;
- 二者不可能相交(因为两个相交链表尾节点必相同,然而无环单链表尾节点指向None,有环单链表“尾节点”指向一个节点,这与“链表相交”矛盾)
- 若二者皆有环:共有三种可能的相交拓扑结构,分别为:(两有环单链表相交,必共用环,原因还是尾节点必须相同)
- 二者分别成环,但不相交
- 二者共用环,在成环前就相交
- 二者仍共用环,但其中一个是在环上加入另一个环的(见最下图)
- 处理方法:
- 根据第一步的函数得知两个有环单链表的第一个成环Node:loop1&loop2;
- 接下来,若loop1 == loop2,说明二者必然是结构2(下图);接下来当做不成环的两个单链表处理,两单链表尾节点就是loop1(loop2)
- 若loop1 != loop2:
- 遍历链表1,若整个遍历完都没有loop2这个Node,说明是拓扑结构1;
- 遍历链表1,若第一次发现loop2,则说明这是二者第一次相交的Node,而且这个Node是位于环上的,也就是拓扑结构3,返回loop2或者loop1任意一个值。
- 若两单链表都无环:
最后,代码奉上,略带注释,应该已经写得很详细了:
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def getIntersectionNode(self, headA, headB):
"""
:type head1, head1: ListNode
:rtype: ListNode
"""
loopA = self.hasLoop(headA)
loopB = self.hasLoop(headB)
if loopA is None and loopB is None: # 都不成环
return self.noLoop(headA,headB)
elif loopA and LoopB: # 都成环了
if loopA == loopB: # 共用环,且成环前相交:用无环单链表模式处理(拓扑结构2)
return self.noLoop(headA,headB)
else: # 不相等,则遍历其中一个环。若遍历不到说明二者各自成环但不相交(拓扑结构1);遍历到,则是拓扑结构3
curA = loopA.next
while curA != loopA:
if curA == loopB:
return loopB
curA = curA.next
return None
else: # 有一个成环:不可能相交
return None
# 假设不知道该链表有没有环,先做判断:
def hasLoop(self,head):
if head is None or head.next is None or head.next.next is None:
return None # 米有环
fast,slow = head.next.next,head.next
while fast != slow:
fast = fast.next.next if fast.next and fast.next.next else None # 这里两个判断条件必须注意
slow = slow.next
if fast is None:
return None
fast = head
while fast != slow:
fast = fast.next
slow = slow.next
return fast
# 无环两个单链表的相交情况:
def noLoop(self,head1,head2):
if head1 is None or head2 is None:
return None
cur1,cur2 = head1,head2
n = 0
while cur1.next:
cur1 = cur1.next
n += 1
while cur2.next:
cur2 = cur2.next
n -= 1
if cur1 != cur2: # 如果两链表尾节点不一样,必无相交
return None
cur1 = head1 if n>0 else head2 # 将长度较长的链表头置为cur1
cur2 = head2 if cur1 == head1 else head1 # 将另一个链表表头置为cur2
n = abs(n) # 这里别忘了把n置换成正数!
while n > 0:
cur1 = cur1.next
n -= 1
while cur1 != cur2:
cur1 = cur1.next
cur2 = cur2.next
return cur1