首页 重启DAY5 快慢指针
文章
取消

重启DAY5 快慢指针

跟着灵茶山艾府大佬学习算法思路的day5,目标是在一个月内掌握hot100

b站链接如下:

环形链表II【基础算法精讲 07】

链表的中间节点 题目描述:

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 1:

img

1
2
3
输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 。

示例 2:

img

1
2
3
输入:head = [1,2,3,4,5,6]
输出:[4,5,6]
解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。

思路:

快慢指针梦开始的地方,根据图示可知,当快指针指向空,或者下一个节点指向空就退出循环。

image-20260421132044224

image-20260421132113639

代码:

1
2
3
4
5
6
7
8
9
var middleNode = function(head) {
    slow = head;
    fast = head;
    while (fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
};

总结:

是其他需要找中点的题目的基础。

环形链表 题目描述:

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false

示例 1:

img

1
2
3
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

img

1
2
3
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

img

1
2
3
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

思路:

如果有环,慢指针会走入环中,而快指针就有机会追上慢指针。那么只要快指针能与慢指针指向同一个节点,就说明有环;否则快指针正常来到末尾,说明无环。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var hasCycle = function(head) {
    if (!head || !head.next) return false; // 这个判断可以删去,即使只有一个节点或无节点,下面的循环一样可以处理
    
    let slow = head;
    let fast = head;
    
    // 快指针每次走两步,需要确保fast和fast.next都不为空
    while (fast && fast.next) {
        slow = slow.next;          // 慢指针走一步
        fast = fast.next.next;     // 快指针走两步
        
        if (slow === fast) {       // 如果相遇,说明有环
            return true;
        }
    }
    
    return false;  // 快指针到达末尾,说明无环
};

总结:

感觉没有太多可说的,放一下灵神的答疑。

image-20260421145431336

环形链表 II 题目描述:

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos-1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:

img

1
2
3
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

img

1
2
3
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

img

1
2
3
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

思路:

这题新增了一个要求,不仅要判断有环,还要找到环的入口,环的入口的寻找就要涉及一些数学推论了。

可设头结点到入口的距离为a,入口到相遇点的距离为b,相遇口到入口的距离为c。可以知道快慢相遇时,慢指针是一定没有走完一圈的,快指针会将慢指针套圈。

image-20260421145922963

image-20260421150139235

这里slow和head都以每次一步的速度前进。

image-20260421150238494

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var detectCycle = function(head) {
    
    let slow = head;
    let fast = head;
    
    // 第一阶段:判断是否有环,找到相遇点
    while (fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
        
        if (slow === fast) {  // 有环
            // 第二阶段:找环的入口
            let ptr1 = head;    // 从链表头开始
            let ptr2 = slow;    // 从相遇点开始
            
            while (ptr1 !== ptr2) {
                ptr1 = ptr1.next;
                ptr2 = ptr2.next;
            }
            
            return ptr1;  // 环的入口
        }
    }
    
    return null;  // 无环
};

总结:

这题相较于上一题判断是否有环,新增了一个循环来找到入口,这个入口的寻找与一个简单的数学推论有关,动笔算一算就比较容易理解了。关键在于要知道在相遇时,快指针有可能已走n圈。

重排链表 题目描述:

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

1
L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

1
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

img

1
2
输入:head = [1,2,3,4]
输出:[1,4,2,3]

示例 2:

img

1
2
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]

思路:

这个重排过程可以理解成两步。第一步是先找到中间节点,反转中间节点后面的链表,得到如下两段链表:

值得注意的是,此时值为3的节点是指向空节点的,因为一开始pre为null,这点是需要注意的。在代码逻辑中也会借助这点来结束重排的循环。

image-20260421192219691

然后逐个处理head和head2,推进这两个指针的同时将head2插入head后面。最后原链表的中点会成为重排后的链表的最后一个节点。

image-20260421192341603

image-20260421192355717

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var reorderList = function(head) {
    let slow = head;
    let fast = head;
    while (fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
    } // slow 此时指向中点

    let pre = null;
    let cur = slow;
    while ( cur !== null) {
        let next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    } // pre 此时指向原链表最后一个节点,也即反转链表的第一个节点

    let head2 = pre;
        while (head2.next !== null) {
            const nxt = head.next;
            const nxt2 = head2.next;
            head.next = head2;
            head2.next = nxt;
            head = nxt;
            head2 = nxt2;
        }
};

思路:

最难的地方在于如何拆解重排逻辑,找到中点后反转再插入以实现的思路并不是那么好想的。

本文由作者按照 CC BY 4.0 进行授权

阅读DAY5 JavaScript高级程序设计 5章中 基本引用类型

阅读DAY6 JavaScript高级程序设计 5章下 基本引用类型