前端刷题路-Day83:相交链表(题号160)

321 阅读4分钟

这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战

相交链表(题号160)

题目

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

图示两个链表在节点 c1 开始相交:

img

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

示例 1:

img

输入: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:

img

输入: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:

img

输入: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
  • 如果 listAlistB 没有交点,intersectVal0
  • 如果 listAlistB 有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]

进阶:

  • 你能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?

链接

leetcode-cn.com/problems/in…

解释

这题啊,这题是简我击失败。

又失败了一次,木得办法,能想到双指针,但是想不到双指针的解法,是在下输了。

常规的解法就比较容易想到了,先扫一遍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
};

只要一直更新pApB的位置即可,如果二者相等,结束循环。

不需要关心当前的值时什么,如果是null,证明两个链表不相交,返回即可,如果是正常节点,也返回就行,因此两个链表相交了。



PS:想查看往期文章和题目可以点击下面的链接:

这里是按照日期分类的👇

前端刷题路-目录(日期分类)

经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇

前端刷题路-目录(题型分类)