python找到相交链表的相交节点元素

该博客介绍了如何使用Python解决寻找两个单链表相交节点的问题,通过三种方法实现:找到长度差、指针追逐和使用集合。文章提供示例代码并分析了不同方法的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题说明

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

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

注意:
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

思路与注意

官方文档描述is为对象标示符(object identity),而==表示的是相等。is的作用是用来检查对象的标示符是否一致,也就是两个对象在内存中的位置是否一样,而==是用来检查两个对象是否相等
因为是找到相同的元素(id相同)所以之前的比较我用的是 is 而不是 ==
但这里可以用 ==,和id结果一样。

参考Leetcode中的答案:

  1. 找到长度差
  2. 指针追逐
  3. 使用集合

找到长度差

class Solution():
    def getIntersectionNode(self, headA, headB):
        """

        :param headA: ListNode
        :param headB: ListNode
        :return: ListNode
        """
        a = b = 0
        h1 = headA
        h2 = headB
        while h1:
            h1 = h1.next
            a +=1
        while h2:
            h2 = h2.next
            b +=1
        h1 = headA
        h2 = headB
        if a >= b:
            for i in range(a - b):
                h1 = h1.next
        else:
            for i in range(b - a):
                h2 = h2.next
        while h1 and h2:
            if h1 is h2:
                break
            h1 = h1.next
            h2 = h2.next
        return h1

指针追逐

class Solution(object):
    def getIntersectionNode(self, headA, headB):
        """
        将两个链表拼接在一起,p是headA->headB
        q是headB->headA
        这样p和q的长度一致,会在相交节点相遇
        要么就都返回None
        :param headA: ListNode
        :param headB: ListNode
        :return: ListNode
        """
        p = headA
        q = headB
        # p 4,1,8,4,5->5,0,1,8,4,5
        # q 5,0,1,8,4,5->4,1,8,4,5

        while p is not q:
            p = p.next if p else headB
            q = q.next if q else headA
        return p

使用集合

class Solution():
    def getIntersectionNode(self, headA, headB):
        """

        :param headA: ListNode
        :param headB: ListNode
        :return: ListNode
        """
        s = set()
        h1 = headA
        h2 = headB
        while h1:
            s.add(h1)
            h1 = h1.next
        while h2:
            if h2 in s:
                break
            h2 = h2.next
        return h2

总结

上面三种方法的时间复杂度逐渐降低

三种方法的耗时,第一个是 使用集合 第二个是 使用距离差 第三个是指针追逐

可供测试用的源代码

"""
160. 相交链表

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

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

注意:
    如果两个链表没有交点,返回 null.
    在返回结果后,两个链表仍须保持原有的结构。
    可假定整个链表结构中没有循环。
    程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

"""
# Definition for singly-linked list.
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
## 指针追逐
class Solution(object):
    def getIntersectionNode(self, headA, headB):
        """
        将两个链表拼接在一起,p是headA->headB
        q是headB->headA
        这样p和q的长度一致,会在相交节点相遇
        要么就都返回None
        :param headA: ListNode
        :param headB: ListNode
        :return: ListNode
        """
        p = headA
        q = headB
        # p 4,1,8,4,5->5,0,1,8,4,5
        # q 5,0,1,8,4,5->4,1,8,4,5

        while p is not q:
            p = p.next if p else headB
            q = q.next if q else headA
        return p
## 因为是找到相同的元素(id相同)所以之前的比较我用的是 is 而不是 ==
## 但这里可以用 ==
# 官方文档描述is为对象标示符(object identity),而==表示的是相等。is的作用是用来检查对象的标示符是否一致,也就是两个对象在内存中的位置是否一样,而==是用来检查两个对象是否相等。


## 用集合set
# class Solution():
#     def getIntersectionNode(self, headA, headB):
#         """
#
#         :param headA: ListNode
#         :param headB: ListNode
#         :return: ListNode
#         """
#         s = set()
#         h1 = headA
#         h2 = headB
#         while h1:
#             s.add(h1)
#             h1 = h1.next
#         while h2:
#             if h2 in s:
#                 break
#             h2 = h2.next
#         return h2

## 距离,容易想到
# class Solution():
#     def getIntersectionNode(self, headA, headB):
#         """
#
#         :param headA: ListNode
#         :param headB: ListNode
#         :return: ListNode
#         """
#         a = b = 0
#         h1 = headA
#         h2 = headB
#         while h1:
#             h1 = h1.next
#             a +=1
#         while h2:
#             h2 = h2.next
#             b +=1
#         h1 = headA
#         h2 = headB
#         if a >= b:
#             for i in range(a - b):
#                 h1 = h1.next
#         else:
#             for i in range(b - a):
#                 h2 = h2.next
#         while h1 and h2:
#             if h1 is h2:
#                 break
#             h1 = h1.next
#             h2 = h2.next
#         return h1


def stringToInt(input):
    return int(input)

def stringToListNode(intersectVal, listA, listB, skipA, skipB):
    # Generate list from the input

    # Now convert that list into linked list
    """

    :param intersectVal: int    intersection node (0 means no intersection between A and B)
    :param listA: list          contains the value of a singly-linked list A
    :param listB: list          contains the value of a singly-linked list B
    :param skipA: int           the number of nodes [start node, intersection node) in A
    :param skipB: int           the number of nodes [start node, intersection node) in B
    :return: tuple              contains two singly-linked lists
    """
    dummyRoot1 = ListNode(0)
    ptr1 = dummyRoot1

    dummyRoot2 = ListNode(0)
    ptr2 = dummyRoot2

    # d1 = (dummyRoot1==dummyRoot2)
    # d2 = dummyRoot1 is dummyRoot2
    a = b = 0

    for number in listA:
        ptr1.next = ListNode(number)
        ptr1 = ptr1.next
        a += 1
        if a == skipA:
            break
    for number in listB:
        ptr2.next = ListNode(number)
        ptr2 = ptr2.next
        b += 1
        if b == skipB:
            break
    intersec = ptr1
    if intersectVal == 0:
        for i in range(a, len(listA)):
            ptr1.next = ListNode(listA[i])
            ptr1 = ptr1.next
        for i in range(b, len(listB)):
            ptr2.next = ListNode(listB[i])
            ptr2 = ptr2.next
    else:
        for i in range(a, len(listA)):
            ptr1.next = ptr2.next = ListNode(listA[i])
            ptr1, ptr2 = ptr1.next, ptr2.next

    return dummyRoot1.next, dummyRoot2.next, intersec.next

def listNodeToString(node):
    if not node:
        return "[]"

    result = ""
    while node:
        result += str(node.val) + ", "
        node = node.next
    return "[" + result[:-2] + "]"

def main():
    line1 = 8
    intersectVal = stringToInt(line1)

    listA = [4,1,8,4,5]
    listB = [5,0,1,8,4,5]

    line3 = 2
    skipA = stringToInt(line3)
    line4 = 3
    skipB = stringToInt(line4)

    headA, headB, intersec =stringToListNode(intersectVal, listA, listB, skipA, skipB)

    ret = Solution().getIntersectionNode(headA, headB)

    out = listNodeToString(ret)

    if intersectVal>0 and ret is intersec:
        print("Intersected at \'%d\'" % intersectVal)
    elif intersectVal==0 and not ret:
        print("No intersection")
    else:
        print("Wrong output")
    print(out)


if __name__ == '__main__':
    main()
### Python 实现相交链表算法 #### 定义链表节点类 为了实现相交链表的检测功能,首先定义一个简单的单向链表节点 `ListNode` 类。 ```python class ListNode: def __init__(self, x): self.val = x self.next = None ``` #### 方法一:标准求解(双层循环) 这种方法通过遍历第一个链表并将所有访问过的节点存储起来,再依次检查第二个链表中的每一个节点是否存在于之前保存的结果集中。时间复杂度较高,为 O(n*m),其中 n 和 m 分别代表两个链表的长度[^1]。 #### 方法二:改进版一(双指针法) 此方法利用了两个指针分别指向两条链表头部并同步前进的特点,在到达各自末端后转向另一条链表继续移动直到相遇为止。这样做的好处是可以平衡掉两者的长度差异从而找到交叉点。该方案的时间复杂度降低到了O(n+m)。 ```python def getIntersectionNode(headA: ListNode, headB: ListNode) -> ListNode: pointerA, pointerB = headA, headB while pointerA is not pointerB: pointerA = headB if pointerA is None else pointerA.next pointerB = headA if pointerB is None else pointerB.next return pointerA # Either the intersection node or both reached end(None) ``` #### 方法三:改进版二(哈希检索-集合) 使用Python内置的数据结构——set来记录已经访问过的第一条链表上的结点地址信息,之后再次遍历第二条链表查看是否有相同位置的对象存在即可快速定位交汇处。这种方式的空间消耗较大但是效率很高,平均情况下可以在接近线性时间内解决问题[^2]。 ```python def getIntersectionNode_with_set(headA: ListNode, headB: ListNode) -> ListNode: visited_nodes = set() current_node = headA while current_node: visited_nodes.add(current_node) current_node = current_node.next current_node = headB while current_node: if current_node in visited_nodes: return current_node current_node = current_node.next return None ``` #### 方法四:最优算法分析 综合考虑时间和空间性能因素,上述三种方式各有优劣。对于实际应用而言,“双指针”策略通常被认为是最优的选择因为它既保持了一定程度上的高效又能较好地控制额外内存开销。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值