给你两个单链表的头节点 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 。
本人思路:
首先遍历两个链表,并依次将两个链表的节点存入队列当中。之后依次拿出两个队列中的尾部,若尾部相同,则表明有公共部分,则取出尾部,继续遍历队列,若不同,则表明不为公共部分,结束遍历。
代码如下:
class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ArrayDeque<ListNode> dequeA = new ArrayDeque<>();
ArrayDeque<ListNode> dequeB = new ArrayDeque<>();
//将链表A的节点依次放置到队列A当中
while (headA!=null){
dequeA.add(headA);
headA=headA.next;
}
//将链表B的节点依次放置到队列B当中
while (headB!=null){
dequeB.add(headB);
headB=headB.next;
}
//如果队列A或队列B有一个为空,则代表对应的链表也为空,直接返回null即可。
if(dequeA.isEmpty()||dequeB.isEmpty()){
return null;
}
ListNode answer=null;
//当队列A不为空,且队列B不为空时
while (!dequeA.isEmpty()&&!dequeB.isEmpty()){
//如果队列A尾部元素与队列B尾部元素相同,则表明有公共部分
if(dequeA.getLast()==dequeB.getLast()){
//更新答案
answer=dequeA.getLast();
//移除队列A与队列B尾部元素
//坑点:ArrayDeque的add()方法将元素添加到队列尾部(即index=elements.length-1 处)
// 而remove()方法却是移除队列头部元素(即index=0处)
// 一度以为我代码写错了
dequeA.removeLast();
dequeB.removeLast();
} else {
//如果队列A尾部元素与队列B尾部元素不同,则表明没有公共部分,跳出循环
break;
}
}
//返回答案
return answer;
}
}
本来觉得自己代码不错,一看结果
执行用时:4 ms, 在所有 Java 提交中击败了 19.32% 的用户
内存消耗:41.5 MB, 在所有 Java 提交中击败了 18.18% 的用户
顿时心肺骤停。
后来本人查看了官方给的题解有两个,无论哪个都比我的好(果然我是菜鸡)
官方题解思路一:
遍历第一个链表,并将其放入hashset中。之后遍历第二个链表,如果hashset中有第二个链表的节点,则表明该点即为相交点,否则继续遍历直到结束。
代码如下:
class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
HashSet<ListNode> set = new HashSet<>();
//遍历链表A,并依次将元素放置入hashset中
while (headA!=null){
set.add(headA);
headA = headA.next;
}
//遍历链表B,如果集合中包含该节点,则该节点即为相交点
//否则继续向下遍历
while (headB!=null){
if(set.contains(headB)){
return headB;
}
headB = headB.next;
}
//找不到相交点,直接返回null
return null;
}
}
测试结果:
执行用时:8 ms, 在所有 Java 提交中击败了 19.03% 的用户
内存消耗:42 MB, 在所有 Java 提交中击败了 10.15% 的用户
这个结果貌似还不如我的…
接下来讲述官方题解思路二(下面直接拿原文):
当链表 headA \textit{headA} headA 和 headB \textit{headB} headB 都不为空时,创建两个指针 pA \textit{pA} pA 和 pB \textit{pB} pB,初始时分别指向两个链表的头节点 headA \textit{headA} headA 和 headB \textit{headB} headB,然后将两个指针依次遍历两个链表的每个节点。具体做法如下:
每步操作需要同时更新指针 pA \textit{pA} pA 和 pB \textit{pB} pB;
如果指针 pA \textit{pA} pA 不为空,则将指针 pA \textit{pA} pA 移到下一个节点;如果指针 pB \textit{pB} pB 不为空,则将指针 pB \textit{pB} pB 移到下一个节点。
如果指针 pA \textit{pA} pA为空,则将指针 pA \textit{pA} pA 移到链表 headB \textit{headB} headB 的头节点;如果指针 pB \textit{pB} pB 为空,则将指针 pB \textit{pB} pB 移到链表 headA \textit{headA} headA 的头节点。
当指针 pA \textit{pA} pA 和 pB \textit{pB} pB 指向同一个节点或者都为空时,返回它们指向的节点或者 null \text{null} null。
那么这段话说的啥意思呢 即
假设链表A为线段A+线段C,链表B为线段B加线段C,则对于指针
pA
\textit{pA}
pA 从线段A头部出发,走到线段C尾部,再跳跃到线段B头部,走到线段C尾部,对于指针
pB
\textit{pB}
pB 从线段B头部出发,走到线段C尾部,再跳跃到线段A头部,走到线段C尾部。
那么对于指针
pA
\textit{pA}
pA 走过的路程即为 线段A+线段C+线段B+线段C,对于指针
pB
\textit{pB}
pB 走过的路程即为 线段B+线段C+线段A+线段C,我们可以发现指针
pA
\textit{pA}
pA 与指针
pB
\textit{pB}
pB 在前三部分走过的路程长度相同,若线段C长度不为0,即有相交部分,那么必定会到第四部分,也就是双方再次走到线段C开头时相遇。则我们可以得到相交点。
代码如下:
class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//如果链表A或链表B为空,直接返回null
if(headA==null||headB==null){
return null;
}
ListNode pA = headA;
ListNode pB = headB;
//当指针A与指针B指向节点不同时
while (pA!=pB){
if(pA!=null){
pA = pA.next;
}else {
pA = headB;
}
if(pB!=null){
pB = pB.next;
}else {
pB = headA;
}
}
return pA;
}
}