剑指Offer 两个链表的第一个公共节点
题目描述
原题链接
输入两个链表,找出它们的第一个公共节点。
如下面的两个链表:
在节点 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。
解题思路
题目分析
这个题,有很多人一来会想着直接用尾端对齐的方式,然后往前找,但是这是一个单链表。所以我们还得想别的方法。
细心的朋友看第一个例子的时候就会发现问题了。公共结点为什么不是 1
?那是因为结点的比较,比较的是地址
,这里理解清楚以后才不至于代码AC了但是心生疑惑了。
官方提示是使用双指针,但是我一开始只想到了先把 headA
遍历一遍,将其存入HashSet ,利用Set的不可重复的特性来求解。
解法一:哈希集合
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
class Solution {
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
HashSet<ListNode> set = new HashSet<ListNode>();
ListNode tmp = headA;
while (tmp != null) {
set.add(tmp);
tmp = tmp.next;
}
tmp = headB;
while (tmp != null) {
if (set.contains(tmp)){
return tmp;
}
tmp = tmp.next;
}
return null;
}
}
这里只要注意一下set里面要存入的是ListNode,而不是成员变量val的值,这个还是容易想出来的,接下来介绍今天的主角——双指针。
解法二:双指针
我们设 headA 到公共结点的距离为 x
, PA指针指向 headA
, headB
到公共结点的距离为 y
,PB
指向headB
,公共结点的长度为 m
。我们让 PA
遍历完 headA
后继续遍历 headB
,让 PB
遍历完headB
之后,再遍历 headB就可以找到公共结点了。 原理是 x + m + y = y + m + x
,再来看力扣官网的证明
流程分析(图片版)
这个超长的流程图看完之后应该会好理解一些了。
接下来我们就直接上代码了:
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
}
总结
这个题目虽然只是一个简单题,但是第二个我确实没想到,看样子还要努力。总结下这个题目的考察点。
- 公共结点的判定
- 循环体代码如何写
- 边界条件的判定(写剑指Offer对这个都刷出肌肉记忆了)
这个题双指针能设置的位置也只有头节点了,按理来说不应该想不出,所以遇到复杂的问题还是应该画图走一走流程,心里就会有方向感了。