记录两道算法题
重排链表
先看一个例子
1 -> 2 -> 3 -> 4
从后面的节点开始,倒着插入到前面的节点。
1 -> 4 -> 2 -> 3
可以看作是将 4 插入 1.next, 将 3 插入 2.next
如果是奇数长度的链表
1 -> 2 -> 3 -> 4 -> 5
1 -> 5 -> 2 -> 4 -> 3
(1)可以看出链表的终点是链表的中间节点
(2)倒数第n个节点成为第n个节点的next
如果用数组来做,下面就可以不用看了,把节点遍历一遍push到数组,然后通过数组下标进行操作,毫无难度。本来数组就是比单向链表方便的。
如果用链表怎么做呢?链表只能看看怎么遍历保留想要的东西。
那么如果我们用while循环来做的话,终止的条件可以是当节点到了中间节点就停下来。
把链表通过中间节点折成两半的话,对折起来刚刚好是对称的,再相加一挤就嵌进去了,就是我们想要的结果。所以我们还需要得到后半部分链表的反转。
需要两个算法的知识点
1. 找到链表的中间节点
2. 反转链表
123456789
-
用快慢指针
链表长度是存在奇数和偶数的,一个在前面的指针走到 null 为止,另一个指针随着前面的指针停下来而停下来,会得到一个相对位置差。这是快慢指针的设计想法。
一个指针走一步,一个指针走两步,他们的差距就会是 2 的倍数。而链表的中间就是链表的一半。两者之间是可以有这么一个联想的。大家可以自己指一下走一遍。
大家可以用5秒钟思考一下,如果一个指针走一步,一个指针走三步。那走一步的指针会停在链表的哪一个位置。
5,2,1。好,如果链表足够长的话,是基本上停在了3分之一的前后。一个没什么用的联想。
let n1 = node, n2 = node
while(n2) {
n1 = n1.next
n2 = n2.next.next
}
- 反转链表-用3个指针 我之前的文章
主要的思路是这样的,用两个变量改变指向。用一个变量保存被斩断的节点。
a -> b -> c -> d -> e -> f
1 2 3
a <- b -- c -> d -> e -> f
1 2 3
a <- b -- c -> d -> e -> f
1 2 3
a <- b <- c -- d -> e -> f
1 2 3
...
重复这样的步骤直到 3 指向 null
代码如下:
let n1 = head, n2 = head.next, n3 = n2
while(n3) {
n3 = n2.next
n2.next = n1
n1 = n2
n2 = n3
}
head.next = null
// n1 将是链表的开头,head将是链表的结尾
那么组合一下,最终的代码是:
function Node(val, next) {
this.val = val
this.next = next
}
// 测试
const a = new Node(1, new Node(2, new Node(3, new Node(4, null))))
console.log(reorderList(a))
function reorderList(head) {
let n1 = head,
n2 = head
while (n2 && n2.next) {
n1 = n1.next
n2 = n2.next.next
}
// 中间节点
const mid = n1
// 复用了变量
n2 = mid.next
let n3 = n2
// 反转链表
// 这里是从中间节点开始。反转后半部分的链表
while (n3) {
n3 = n2.next
n2.next = n1
n1 = n2
n2 = n3
}
// 解决 相互指向
mid.next = null
n2 = head
// 从头遍历到中间节点停下来
while (n2 !== mid) {
n3 = n2.next
// 当链表长度是偶数的时候,会少插入一次
// 比如 中间节点是 3,
// 1 -> 2 -> 3 -> 4
// 1 -> 4 -> 2 -> 3
if (n1 === n3) {
break
}
// 将后半部分节点加入到对应节点的next
// 1 -> 2
// 4 -> 3
// 1 -> 4 -> 2 -> 3
n2.next = n1
const temp = n1.next
n2 = n1.next = n3
n1 = temp
}
return head
}
环路检测
这道题用一个 map 进行标记已经出现过的节点,当第二次出现的时候,这个节点就是答案,没有就是null。
function detectCycle(head) {
const map = new Map()
let node = head
while (node) {
if (map.has(node)) {
return node
} else {
map.set(node, true)
}
node = node.next
}
return null
}
还有一直不生成额外空间的方法,需要列等式,还没理清楚。快慢指针有闭环是一定会相遇的,但和环路的开头节点有什么样的联系还有待解决。到时候有机会更新这篇文章。