leetcode热题——链表相交

链表相交

题目描述

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交: 

 题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

示例 1:

 输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3

输出:Intersected at '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

输出:Intersected at '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 。

提示

listA 中节点数目为 m
listB 中节点数目为 n
0 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
如果 listA 和 listB 没有交点,intersectVal 为 0
如果 listA 和 listB 有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]

进阶:你能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?

方法一

解题思路

简单来说,就是求两个链表交点节点的指针。 这里要注意,交点不是数值相等,而是指针相等。

为了方便举例,假设节点元素数值相等,则节点指针相等。

看如下两个链表,目前curA指向链表A的头结点,curB指向链表B的头结点: 

我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,如图: 

此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。

否则循环退出返回空指针。

代码实现

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0;
        while (curA != NULL) { // 求链表A的长度
            lenA++;
            curA = curA->next;
        }
        while (curB != NULL) { // 求链表B的长度
            lenB++;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        // 让curA为最长链表的头,lenA为其长度
        if (lenB > lenA) {
            swap (lenA, lenB);
            swap (curA, curB);
        }
        // 求长度差
        int gap = lenA - lenB;
        // 让curA和curB在同一起点上(末尾位置对齐)
        while (gap--) {
            curA = curA->next;
        }
        // 遍历curA 和 curB,遇到相同则直接返回
        while (curA != NULL) {
            if (curA == curB) {
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }
};

复杂度分析

时间复杂度:O(m+n)

空间复杂度:O(1)

方法二

解题思路

这道题目用双指针法可以使代码更简洁,算法如下:

  • 两个指针 pA 和 pB 分别从 headA 和 headB 出发。

  • 当一个指针走到链表尾部时,跳转到另一个链表的头部继续走。

  • 因为两条链表长度差值被均衡了,所以如果有交点,第二次相遇即为交点。

  • 如果最终没有交点,两个指针会同时走到 nullptr,终止循环。

以下图为例:

A: a1 → a2 -> a3 ->a4
                     ↘
                       c1 → c2 → c3
                     ↗
B:          b1 → b2

链表 A 长度为 m = a + c 链表 B 长度为 n = b + c 其中 c 是公共部分长度。

思路核心:
pA 从 headA 开始,走完链表 A 后跳到 B。

pB 从 headB 开始,走完链表 B 后跳到 A。

两人走的总长度相同:a + c + b 和 b + c + a

因此他们一定在第一个交点或 nullptr 相遇。

模拟图示走位过程:

步数pA位置pB位置
0a1b1
1a2b2
2a3c1 ← 相交点
3a4c2
4c1c3
5c2nullptr(结束)
6c3headA(跳转)
7nullptr(结束)a1
8headB(跳转)a2
9b1a3
10b2a4
11c1 ← 相遇!c1 ← 相遇!

关键理解:

跑道例子:

  • 跑道 A 长 100 米(a = 100)

  • 跑道 B 长 150 米(b = 150)

  • 公共终点段为 50 米(c = 50)

  • A 选手跑:100(私有) + 50(公共)+ 150(对方私有)

  • B 选手跑:150(私有) + 50(公共)+ 100(对方私有)

  • 两人跑的总路程是一样的(a + b + c)

他们错开出发,交换跑道后总距离一致,自然会在公共段的起点相遇。

代码实现

class Solution {
public:
    ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
        // 如果有一个链表为空,直接返回空
        if (headA == nullptr || headB == nullptr) {
            return nullptr;
        }

        // 初始化两个指针,分别指向两个链表的头节点
        ListNode* pointerA = headA;
        ListNode* pointerB = headB;

        // 两个指针同步前进,直到相遇或者都为 null
        while (pointerA != pointerB) {
            // 如果 pointerA 走到末尾,就跳到 headB
            if (pointerA == nullptr) {
                pointerA = headB;
            } else {
                pointerA = pointerA->next;
            }

            // 如果 pointerB 走到末尾,就跳到 headA
            if (pointerB == nullptr) {
                pointerB = headA;
            } else {
                pointerB = pointerB->next;
            }
        }

        // 返回相遇的节点(可能是交点,也可能是 nullptr)
        return pointerA;
    }
};

复杂度分析

时间复杂度:O(m+n)

空间复杂度:O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值