中间结点和倒数第 k 个结点类似,区别就是如何判断 slow 的结点何时应该继续走
可以推理出,当 slowLen * 2 > fastLen 的时候,说明现在的位置就是中间结点
function middleNode(head: ListNode | null): ListNode | null {
// 使用虚拟头结点,简化逻辑
const dummyHead = new ListNode()
dummyHead.next = head
// slow 和 fast 指针都是从虚拟结点开始
// 也就是从 len 0 开始
let slow = dummyHead
let slowLen = 0
let fast = dummyHead
let fastLen = 0
// 将链表遍历完
while (fast) {
// 也就是说 2 * slowLen > fastLen 才行
if (2 * slowLen <= fastLen) {
slow = slow.next
slowLen += 1
}
fast = fast.next
fastLen += 1
}
return slow
}
和上面的思路一样,不过可以不使用虚拟结点,从 len 为 1 开始遍历
function middleNode(head: ListNode | null): ListNode | null {
// slow 和 fast 指针都是 head 开始
// 也就是从 len 1 开始
let slow = head
let slowLen = 1
let fast = head
let fastLen = 1
// 需要将链表遍历完
while (fast) {
// 也就是说 2 * slowLen > fastLen 才行
if (2 * slowLen <= fastLen) {
slow = slow.next
slowLen += 1
}
fast = fast.next
fastLen += 1
}
return slow
}
继续分析 slowLen * 2 > fastLen,可得实际上就是快指针走两步,慢指针走一步。
这样每一步慢指针都停在当前的中间结点,所以初始化可以都用 head,不需要引入虚拟头结点来简化逻辑
这样可以不用额外通过 len 来判断
function middleNode(head: ListNode | null): ListNode | null {
let slow = head
let fast = head
// 因为一开始都走了一步,
// 所以实际走的奇偶数翻转过来了
// 1. 对于奇数个结点,会落在 fast.next 不存在的条件下
// 此时 slow 是中间结点,不需要再遍历了
// 2. 对于偶数数个结点,会落在 fast 不存在的条件下
// 此时 slow 落在了两个中间结点的后面一个结点,也不需要再遍历了
while (fast && fast.next) {
// 快指针走两步,慢指针走一步
slow = slow.next
fast = fast.next.next
}
return slow
}