【剑指Offer】52. 两个链表的第一个公共节点 解题报告(Python)

62 篇文章 2 订阅
58 篇文章 36 订阅

题目地址:https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/

题目描述

输入两个链表,找出它们的第一个公共节点。

如下面的两个链表:

在节点 c1 开始相交。

示例 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) 内存。
  • 本题与主站 160 题相同:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/

解题方法

本题有两种主要的方法:

  • 使用额外空间(栈、HashSet)
  • 不使用额外空间

方法一:栈

第一个公共节点,相当于是从后往前找第一个不公共的。

所以可以用栈来做:使用两个栈,把两个链表的所有节点分别入栈。然后同时出栈,出栈的时候刚开始都是公共的节点,直到找到最后一个公共的节点。

python 代码:

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def FindFirstCommonNode(self, pHead1, pHead2):
        stack1 = []
        stack2 = []
        while pHead1:
            stack1.append(pHead1)
            pHead1 = pHead1.next
        while pHead2:
            stack2.append(pHead2)
            pHead2 = pHead2.next
        pre = None
        while stack1 and stack2 and stack1[-1] == stack2[-1]:
            pre = stack1.pop()
            stack2.pop()
        return pre

方法二:HashSet

也可以用 HashSet 保存一条链表的所有节点。然后对另一条链表进行遍历,判断节点是否在 HashSet 中,如果是的话,就说明是公共节点。

C++ 代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode*> visited;
        ListNode* move = headA;
        while (move) {
            visited.insert(move);
            move = move->next;
        }
        move = headB;
        while (move) {
            if (visited.count(move))
                return move;
            move = move->next;
        }
        return nullptr;
    }
};

方法三:不使用额外空间

不使用额外空间的方法,巧妙地使用了数学方法。

假设:

  • 链表 A 前面的非公共部分长度是 m
  • 链表 B 前面的非公共部分长度是 n
  • 链表 A 和 B 的公共部分长度是 x

使用两个指针 moveA 和 moveB,同时从链表 A 和 链表 B 的开头向后遍历。

假设链表 A 更短,即 m < n,那么当 moveA 已经遍历到链表 A 结尾的时候,则把它移动到链表 B 的开头继续遍历;当 moveB 已经遍历到链表 B 结尾的时候,则把它移动到链表 A 的开头继续遍历。

这样能保证两个指针相交的时候,即为第一个公共节点。

证明如下:

当两个指针相交的时候,分为 3 种情况——

  1. m == n ,即非公共部分长度相等,那么当两个链表还没有遍历完成一次的时候,两个指针就会相遇。
  2. m < n,那么 moveA 会把链表 A 先遍历结束(moveA 走了 m + x 长度,此时 moveB 还有 (n + x) - (m + x) = n - m 的长度到达链表尾部),把 moveA 移动到链表 B 的开头;当 moveB 把链表 B 遍历结束时(此时 moveB 和 moveA 都走了 n - m 的长度),moveB 移动到链表 A 的开头。当两个指针再往前走 m 步,那么恰好 moveA 处于链表 B 的 n - m + m = n 的位置,moveB 处于链表 A 的 m 的位置。所以两者会相遇在第一个公共节点上。
  3. m > n,同上。

代码里面需要注意的地方是需要当 moveA 和 moveB 都是空的时候(而不是 moveA->next 为空),才移动到另外一条链表的头部。否则,当两条链表没有交点的时候,会陷入死循环。

C++ 代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* move1 = headA;
        ListNode* move2 = headB;
        while (move1 != move2) {
            move1 = move1 ? move1->next : headB;
            move2 = move2 ? move2->next : headA;
        }
        return move1;
    }
};

日期

2018 年 3 月 22 日 —— 睡了个懒觉,感觉好幸福。。
2021 年 7 月 22 日 —— 昨天提了离职

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值