题目链接:https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/
1. 题目介绍(52. 两个链表的第一个公共节点)
输入两个链表,找出它们的第一个公共节点。
如下面的两个链表:
在节点 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. 相交链表 题目相同.
2. 题解
2.1 枚举 – O(mn)
时间复杂度O(mn),空间复杂度O(1)
【解题思路】:
枚举法:在第一个链表上顺序遍历每个节点,每遍历到一个节点,就在第二个链表上顺序比哪里每个节点。如果在第二个链表上有一个节点和第一个链表上的节点一样,则说明两个链表在这个节点上重合,于是就找到了它们的公共接待你。
……
【实现策略】:
- 先进行无效输入判断;
- 创建一个临时节点
tmpB
,用来保存链表B
的头节点;- 双重循环,找出在第二链表上第一个与第一链表相同的节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
class Solution {
// Solution1:枚举
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 无效输入判断
if (headA == null || headB == null) return null;
// 保存 headB 的头节点
ListNode tmpB = headB;
// 循环判断 A 中的节点是否有和 B 中节点相同的
while (headA != null) {
// 让 headB 回到头节点上来
headB = tmpB;
while (headB != null) {
if (headA == headB) return headA;
// 不同的话,就让 B 移动到下一个节点
headB = headB.next;
}
headA = headA.next;
}
// 两个链表没有公共节点,返回null
return null;
}
}
2.2 辅助栈 – O(m+n)
时间复杂度O(m+n),空间复杂度O(n)
【解题思路】:
如果我们从两个链表的尾部开始往前比较,那么最后一个相同的节点就是我们要找的节点。我们可以通过栈来解决这个问题:分别把两个链表的节点放入两个栈里,这样两个链表的尾节点就位于两个栈的栈顶,接下来比较两个栈顶的节点是否相同。如果相同,则把栈顶弹出接着比较下一个栈顶,直到找到最后一个相同的节点。
……
【实现策略】:
- 遍历两个链表,并分别将链表存入两个辅助栈中;
- 将两个辅助栈分别出栈,找出最后一个共同节点,该节点即为第一个公共节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
class Solution {
// Solution2:辅助栈
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 无效输入判断
if (headA == null || headB == null) return null;
// 定义两个辅助栈,用来分别存储链表 A 和 B
Stack<ListNode> stackA = new Stack<>();
Stack<ListNode> stackB = new Stack<>();
// 遍历链表A,并将节点全部存入stackA
while (headA != null) {
stackA.push(headA);
headA = headA.next;
}
// 遍历链表B,并将节点全部存入stackB
while (headB != null) {
stackB.push(headB);
headB = headB.next;
}
ListNode pre = null;
// 将两个辅助栈分别出栈,找出最后一个共同节点,该节点即为第一个公共节点
while (!stackA.isEmpty() && !stackB.isEmpty()) {
// 如果 A 和 B 当前的栈顶元素不同,则直接返回栈顶节点
if (stackA.peek() != stackB.peek()) break;
// 如果相同,则弹出
pre = stackA.pop();
stackB.pop();
}
// 两个链表没有公共节点,返回null
return pre;
}
}
2.3 双指针 – O(m+n)
时间复杂度O(m+n),空间复杂度O(1)
【解题思路】:
我们使用两个指针node1
,node2
分别指向两个链表headA
,headB
的头结点,然后同时分别逐结点遍历,当node1
到达链表headA
的末尾时,重新定位到链表headB
的头结点;当node2
到达链表headB
的末尾时,重新定位到链表headA
的头结点。
这样,当它们相遇时,所指向的结点就是第一个公共结点。
……
如上图所示:
headA
到首个公共节点的距离为a-c
;headB
到首个公共节点的距离为b-c
;……
- 指针
A
先遍历完链表headA
,再开始遍历链表headB
,当走到 首个公共节点 时,共走步数为a+(b−c)
;- 指针
B
先遍历完链表headB
,再开始遍历链表headA
,当走到 首个公共节点 时,共走步数为:b+(a−c)
;- 此时,我们就可以得到:
a+(b−c)=b+(a−c)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
class Solution {
// Solution3:
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 无效输入判断
if (headA == null || headB == null) return null;
ListNode A = headA, B = headB;
while (A != B) {
A = A != null ? A.next : headB;
B = B != null ? B.next : headA;
}
// 两个链表没有公共节点,返回null
return A;
}
}
3. 参考资料
[1] 图解 双指针法,浪漫相遇
[2] 剑指 Offer 52. 两个链表的第一个公共节点(双指针,清晰图解)