这是我参与8月更文挑战的第 12 天,活动详情查看:8月更文挑战
题目链接
题目描述
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
测试用例
输入: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 个节点。
注:如果不相交,返回 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]
题目分析
两个链表,判断相交,如果用数组分别去存储他们的每一个节点的话,我们从最后面开始往前比较,就能知道是否相交,若相交的话是赞一个节点(谁让这链表是单向的呢 ┑( ̄Д  ̄)┍)
尝试解答
只能说,勉强跑出来了,效果非常不理想
需要吧链表转化成数组,需要占用操作时间,操作后产生的数组,占用了额外的控件空间
var getIntersectionNode = function(headA, headB) {
let a = [],
b = [];
while (headA != null) {
a.unshift(headA);
headA = headA.next;
}
while (headB != null) {
b.unshift(headB);
headB = headB.next;
}
if (a.length == 0 || b.length == 0 || a[0] != b[0]) return null;
let n;
for (; a[0] == b[0] && a[0] != null; a.shift(), b.shift()) {
n = a[0];
}
return n;
};
优化总结
先上结论, hash yyds
先将第一个链表转化为 hash 结构,然后遍历访问第二个链表,借助 hash 优秀的机制来对元素存在进行判定
性能方面比上一个略好一点,但内存占用就少了一半!毕竟只有一个额外的 Set 来存储数据
var getIntersectionNode = function(headA, headB) {
if (headA == null | headB == null) return null;
let a = new Set();
while (headA != null) {
a.add(headA)
headA = headA.next;
}
let node = null;
while (headB != null) {
if (a.has(headB)) {
node = headB;
break;
}
headB = headB.next;
}
return node;
};
一个有意思的题解
有人提到了,两个链表比较公共项,其实,最大的困难就是两个链表长度不对应,没法同时 head=head.next 来比较节点,那么,直接就先遍历两个链表,得到长度n, m,然后让最长的那个链表先往前走|n-m| 步,现在两个链表就能同步往后走了
官方最佳双指针题解
基于上一个题解的思路,让两个链表长度相同了,那不就能同时从头往后走了?
那么,我们用额外计数的方式,让两个链表变成a+b, b+a 的形式,那不就能同时往后遍历了?仅需要2个指针和2个标记,不再需要额外的空间进行数据存储
思路就是 pa 指针先遍历 a 链表,等 a 链表遍历完了,修改 flaga 标记,再去遍历 b 链表,结束
pb 指针先遍历 b,完成后,修改 flagb,转头遍历 a 链表,直到结束
var getIntersectionNode = function(headA, headB) {
let flaga = true,
flagb = true;
for (let a = headA, b = headB; a != null || b != null; a = a.next, b = b.next) {
if (a == null && flaga) {
a = headB;
flaga = false;
}
if (b == null && flagb) {
b = headA;
flagb = false;
}
if (a == b) {
return a;
}
}
return null;
};
上一份代码看着通俗易懂,就是有点啰嗦
思索半天,小小的优化亿把,修改标记的类型,将判断整合到 for 里面
var getIntersectionNode = function(headA, headB) {
let n = 1,
m = 1;
for (let a = headA, b = headB; a != null || b != null;
a = a.next || (n-- > 0 ? headB : null),
b = b.next || (m-- > 0 ? headA : null)) {
if (a == b) {
return a;
}
}
return null;
};
我觉得这一版里面最优秀的设计就是用到了|| 的断路功能,虽然每次循环都有,但实际上就执行过 2 次