【剑指offer】JZ52-两个链表的第一个公共节点-Python解法

1.题目描述

2.解题思路(Python版)

首先分析题目,对于两个单向链表来说,如果从某一个节点开始这两个链表出现了公共节点,那么这两个链表的后续节点都是相同的,不可能再出现分叉,因此就像题目描述里所绘制的图形一样,两个有公共节点而部分重合的链表,其拓扑结构只可能是Y型而不是X型。

首先想到的解决问题的方法就是直接遍历,每当遍历到链表1中的一个结点时,就将链表2整个遍历一遍,如果在链表2中发现与链表1中的当前节点一样的节点,也就找到了两个链表的公共节点,如果没有的话,就继续移动到链表1的下一个节点,直到遍历完整个链表1。这种方法的优势是逻辑比较简单,也不耗费额外的空间,但其时间复杂度较高。

为了改进上述方法,进一步想到如果两个链表有公共节点,那么其尾部一定是重合的,如果可以从两个链表的尾部开始往前比较,那么最后一个相同的节点就是我们要找的第一个公共节点,对于单向链表来说,要想先对尾结点作比较,可以借助栈LIFO的特点:将两个链表分别存储到两个辅助栈中,然后分别比较两个栈顶的节点是否相同,如果相同就取出,比较下一个栈顶,直到找到最后一对相同的栈顶,这就是原始两个链表的第一个公共节点。借助栈的方法比起最开始的直接遍历法来说,其时间效率有所提升,但空间消耗增大。

为了进一步均衡时间和空间效率,可以采用遍历两次的方法,思路如下:借助栈来解决上述问题之所以方便,是因为其可以同时遍历到达两个链表的尾结点,当两个链表的长度不一致时,我们同时从头开始遍历就无法同时到达尾节点。因此,可以采用遍历两次的方法:第一次遍历分别得到两个链表的长度,然后计算出长链表比断链表多出来的长度L;第二次遍历先在较长的链表上走L步,然后两者同时遍历,直到找到一个相同的节点。

假设两个链表的长度分别为m和n,上述三种方法的时间复杂度和空间复杂度分别如下表所示:

时间复杂度空间复杂度
直接遍历O(mn)O(1)
借助栈O(m+n)O(m+n)
遍历两次O(m+n)O(1)

比较后发现只有第三种方法满足题目中的复杂度要求,因此采用第三种方法解题。

此外还可以使用哈希表或双指针的方法解题,待学习后补充。

方法一:遍历两次

思路:

1.编写一个计算链表长度的函数,并使用该函数计算链表1、链表2的长度;

2.得到两个链表的长度差,先在长链表上走L步,然后再同时开始遍历;

3.遍历的循环条件是:长链表当前节点不为空+短链表当前节点不为空+两个链表当前节点不相同,若不满足上述条件则跳出循环,跳出循环的可能有以下两种:一是发现公共节点,二是两个链表均循环到结尾,最后将跳出循环前两个链表的最后一个节点输出(要么是第一个公共节点开始的后续链表,要么是一个空链表)

参考代码:

# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

#
# 
# @param pHead1 ListNode类 
# @param pHead2 ListNode类 
# @return ListNode类
#
class Solution:
    def FindFirstCommonNode(self , pHead1 , pHead2 ):
        # write code here
        #得到两个链表的长度
        Length1 = self.GetListLength(pHead1)
        Length2 = self.GetListLength(pHead2)
        if Length1 >= Length2:        #第一次遍历,比较两者长度
            pLongList = pHead1
            pShortList = pHead2
            LengthDif = Length1 - Length2
        else:
            pLongList = pHead2
            pShortList = pHead1
            LengthDif = Length2 - Length1
        for i in range(LengthDif):
            pLongList = pLongList.next      #先在较长的链表上走几步,保证两个链表剩余部分长度相同
        while pLongList and pShortList and pLongList != pShortList:
            pLongList = pLongList.next 
            pShortList = pShortList.next 
        pFirstCommonNode = pLongList
        return pFirstCommonNode

    #定义计算链表长度函数
    def GetListLength(self , pHead):
        Length = 0
        pNode = pHead
        while pNode:
            Length = Length + 1
            pNode = pNode.next
        return Length

该方法需要注意的是循环条件的设置,以及对特殊情况的分析,如两个链表中至少有一个空链表,或者遍历到尾节点都没有发现公共节点等。

复杂度:

时间复杂度O(N):N为两个链表的长度之和,分别对两个链表做了两次遍历;

空间复杂度O(1):在运行过程中没有借助额外的辅助空间。

为提升解决其他问题的能力,现将前两种方法的解题方法阐述如下:

方法二:蛮力法(直接遍历)

思路:

1.从链表1的第一个节点开始顺序遍历,每遍历一个节点,就将链表2中所有节点与其比较一遍,如果发现与当前节点相同的节点,则输出该节点作为首个公共节点,如没有相同的节点,移动到链表1的下一个节点,重复上述流程;

2.若整个链表1遍历完都未发现相同节点,则说明两个链表无公共节点。

参考代码:

# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

#
# 
# @param pHead1 ListNode类 
# @param pHead2 ListNode类 
# @return ListNode类
#
class Solution:
    def FindFirstCommonNode(self , pHead1 , pHead2 ):
        # write code here
        if pHead1 is None or pHead2 is None:
            return None
        cur1 = pHead1    #创建两个指针,分别指向两个链表的头节点
        while cur1:
            cur2 = pHead2
            while cur2:      #固定链表1节点,顺序遍历链表2中的节点
                if cur1 == cur2:
                    return cur1
                cur2 = cur2.next
            cur1 = cur1.next
        return None

复杂度:

时间复杂度O(N²):N为两个链表的长度之和,当链表较长时花费的时间很多。

空间复杂度O(1):在运行过程中没有借助额外的辅助空间。

方法三:借助栈

思路:

将两个链表分别存储到两个辅助栈中,分别比较两个栈顶的节点是否相同,如果相同就取出,比较下一个栈顶,直到找到最后一对相同的栈顶,这就是原始两个链表的第一个公共节点。

参考代码:

# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

#
# 
# @param pHead1 ListNode类 
# @param pHead2 ListNode类 
# @return ListNode类
#
class Solution:
    def FindFirstCommonNode(self , pHead1 , pHead2 ):
        # write code here
        if pHead1 is None or pHead2 is None:
            return None
        stack1 = []    #创建两个辅助栈
        stack2 = []
        while pHead1:
            stack1.append(pHead1)
            pHead1 = pHead1.next
        while pHead2:
            stack2.append(pHead2)
            pHead2 = pHead2.next
        pCommonNode =  None
        while stack1 and stack2:     #比较两个栈顶的节点
            pNode1 = stack1.pop()
            pNode2 = stack2.pop()
            if pNode1 is pNode2:
                pCommonNode = pNode1
            else:
                break
        return pCommonNode

复杂度:

时间复杂度O(N):N为两个链表的长度之和,遍历两个链表所花费的时间。

空间复杂度O(N):N为两个链表的长度之和,也是两个辅助栈所花费的空间。

3.相关知识点

(1)用哈希表、双指针法解题;

(2)两个存在公共节点的链表的拓扑形状和一棵树的形状非常相似,只是这里的指针是从叶节点指向根节点的。两个链表的第一个公共节点正好就是二叉树中两个叶节点的最低公共祖先。

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值