这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战
相交链表(题号160)
题目
给你两个单链表的头节点 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)
内存的解决方案?
链接
解释
这题啊,这题是简我击失败。
又失败了一次,木得办法,能想到双指针,但是想不到双指针的解法,是在下输了。
常规的解法就比较容易想到了,先扫一遍A链表,将当前节点放到Set
或者Map
中,实在懒得操作用对象也行。因为用这些数据类型的原因是其查找操作是O(1)
的复杂度,不需要再进行复杂的查找操作。
推荐使用Set
,因为Set
不用存储值,而Map
和对象都需要存储值。
拿到A所有的值后开始遍历B,如果B中某个节点在Set
中出现了,那就证明B和A有交叉点,返回当前节点即可,如果B链表走到头还没有遇到交叉点,就返回null
,证明两个链表不交叉。
这个思路确实很简单,正常人也都能想到,难就难在双指针解法。
双指针也能想到,但具体怎么解呢?本来是想用快慢指针的,结果想了半天,代码写了一大堆,逻辑及其复杂,还是没有想到结果。
正解其实是另外一种形式的快慢指针,当然了也可以不这么理解。
设想有两个链表,它们长这样:
A: a1 -> a2
-> c1 -> c2 -> c3
B: b1 -> b2 -> b3
A和B在c1
节点相交了,B比A长一个节点,如果在一开始让B指针比A指针多走一步,那么两个指针是不是会同时指向c1
节点?那么此时答案是不是就出来了?
如果两个指针走到头了,都会指向null
,那么这题的答案也出来了,也就是两个链表不相交。
到这里,要解决的问题就只有一个了——如果确定两个链表长度的差值,也就是某一方要领先另一方几个节点的位置。
其实有一种很简单,也很巧妙的方法。
首先让A指针和B指针同时出发,A指针走到头的时候指向B链表的头节点,B指针走到头的时候指向A链表的头节点。
还是上面的例子,A指针走到头的时候会指向B链表的b1
节点,此时B指针还没走到头,等B指针走到头的时候,指向了A链表的a1
节点,此时A指针已经走到了b2
节点。
那么A指针是不是领先了B指针两个链表长度差的单位了?再走一次全部长度就可以求到最终解了。
交换位置这波确实没想到,一下子就完成了快慢指针的快和慢两个位置,一步到位。
这题还有一点需要注意的是测试用例的编写,链表的结构再JavaScript中是对象,要想两个对象相等,它们必须指向同一个内存地址,否则即使值相同,这两个对象也是不相等的,所以测试用例推荐这样写:
const COMMON = {"val":8,"next":{"val":4,"next":{"val":5,"next":null}}}
const A = {"val":4,"next":{"val":1,"next": COMMON}}
const B = {"val":5,"next":{"val":6,"next":{"val":1,"next": COMMON}}}
A
链表和B
链表都引用了COMMON
链表,这样后续循环到制定位置时即可利用相等判断。
自己的答案(Set
)
Set
的做法很简单👇:
var getIntersectionNode = function(headA, headB) {
const aSet = new Set()
while (headA) {
aSet.add(headA.val)
headA = headA.next
}
while (headB) {
if (aSet.has(headB.val)) return headB
headB = headB.next
}
return null
};
这没啥可说的,思路和解释中说的一样,不多赘述。
更好的方法(双指针)
双指针的话看上去要更简单些,可能是因为少了存储到Set
中这一步吧。
var getIntersectionNode = function(headA, headB) {
if (!headA || !headB) return null
let pA = headA
let pB = headB
while (pB !== pA) {
pA = pA ? pA.next : headB
pB = pB ? pB.next : headA
}
return pA
};
只要一直更新pA
和pB
的位置即可,如果二者相等,结束循环。
不需要关心当前的值时什么,如果是null
,证明两个链表不相交,返回即可,如果是正常节点,也返回就行,因此两个链表相交了。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇