【python数据结构与算法】链表——链表中环的入口节点、两个链表的第一个公共节点(相交链表)

如题,这类问题在LeetCode上和剑指offer上总共有这些涉及:

  1. LeetCode:141142160
  2. 剑指offer:两个链表的第一个公共节点(默认是无环单链表)、链表中环的入口节点
  3. 补充:两个未知是否有环的单链表第一个公共节点

我直接叙述第三个问题,也就是补充问题。因为问题3包含了:判断链表是否有环(输出环的入口节点)、无环单链表相交的问题,以及两个有环单链表的相交问题、一个有环一个无环单链表的相交问题

以下,正文开始:


提示:无环单链表和有环单链表的相交是不同的问题,所以要先分析链表是否成环

  1. 第一步,判断单链表是否成环,并返回成环的第一个Node:
    1. 用dict:直接将单链表的节点挨个存入dict。若遍历到某个节点存在,说明成环,该节点就是成环第一个Node;遍历完后没有任何一个节点存在于这个dict中,说明没有环
    2. 不用dict:快慢指针。快指针一次2步,慢指针一次1步;快慢指针从链表头结点一起出发,若快指针走到None了,说明单链表肯定没有环;如果快慢指针相遇,则说明快指针将慢指针套圈,也就是说二者是在环上相遇,此时快指针回到链表头结点然后一次1步向后走,再次遇到慢指针时,该点就是成环第一个Node(玄学
  2. 第二步:分情况讨论有无环的两个单链表相交问题:
    1. 先调用上面第一步的函数,传入两个单链表头结点head,得到是否成环以及成环的第一个Node(不成环,返回None)
    2. 判断两个链表是否有环,并以此下面进行分类讨论
      1. 若两单链表都无环:
        1. 用dict:还是老方法,遍历链表1,将链表1节点都存入dict中。然后遍历链表2,如果遍历到结尾都没发现链表2的Node在dict中存在,则此两者不相交;若第一次遍历到链表2的Node在dict中,这个Node就是二者相交的第一个Node
        2. 不用dict:分别遍历链表1,2,得到两个链表长度之差delta和两个链表的尾节点。若二者尾节点不相同,则说明二者必然不相交;若尾节点相同,则说明相交。此时让长度较长的链表先走完二者长度的差值这些节点,然后二者一起遍历,直到出现相同Node,这就是相交节点
      2. 若一链表有环,一链表无环;
        1. 二者不可能相交(因为两个相交链表尾节点必相同,然而无环单链表尾节点指向None,有环单链表“尾节点”指向一个节点,这与“链表相交”矛盾)
      3. 若二者皆有环:共有三种可能的相交拓扑结构,分别为:(两有环单链表相交,必共用环,原因还是尾节点必须相同)
        1. 二者分别成环,但不相交
        2. 二者共用环,在成环前就相交
        3. 二者仍共用环,但其中一个是在环上加入另一个环的(见最下图)
        4. 处理方法:
          1. 根据第一步的函数得知两个有环单链表的第一个成环Node:loop1&loop2;
          2. 接下来,若loop1 == loop2,说明二者必然是结构2(下图);接下来当做不成环的两个单链表处理,两单链表尾节点就是loop1(loop2)
          3. 若loop1 != loop2:
            1. 遍历链表1,若整个遍历完都没有loop2这个Node,说明是拓扑结构1;
            2. 遍历链表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

猜你喜欢

转载自blog.csdn.net/weixin_41712499/article/details/85686869
今日推荐