009 单双链表及其反转-堆栈诠释

这篇博客主要讲解链表相关知识,首先强调了按值传递与按引用传递的区别。然后介绍了单链表和双链表的定义,并重点通过 LeetCode 206和92 题详细讲解了链表反转的迭代解法,展示了使用指针调整链表结构的思路,最后给出了双链表反转的迭代代码。作者认为链表题是检验编码能力的重要手段。

Posted by Hilda on March 20, 2025

概览:

1)按值传递、按引用传递 (我不知道为什么如此多的同学会犯这种错误,这完全是语言问题)

2)单链表、双链表的定义

3)根据反转功能,彻底从系统角度解释链表是如何调整的

链表题目在笔试、面试中的意义就是检验coding能力如何

更难的题目会在【必备】课程里讲述


1 按值传递、按引用传递

左老师讲了Java中的按值传递、按引用传递。结合我个人的一些理解,我将其书面的表达总结了。参考博客:java只有按值传递

2 单链表和双链表的定义

单链表维持val和next

双链表维持val,next和pre

3 力扣206. 反转链表

206. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

img

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

示例 2:

img

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

示例 3:

1
2
输入:head = []
输出:[]

提示:

  • 链表中节点的数目范围是 [0, 5000]
  • -5000 <= Node.val <= 5000

进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?


解法:遍历一次

拿3个指针(cur,pre,next)搞一搞就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode cur = head, pre = null, next;
        while (cur != null){
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;      
    }
}

image-20250318122645834

时间复杂度O(n),空间复杂度O(1)

解法:递归版

解法:迭代版

力扣92. 反转链表 II

92. 反转链表 II

给你单链表的头指针 head 和两个整数 leftright ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

示例 1:

img

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

示例 2:

1
2
输入:head = [5], left = 1, right = 1
输出:[5]

提示:

  • 链表中节点数目为 n
  • 1 <= n <= 500
  • -500 <= Node.val <= 500
  • 1 <= left <= right <= n

解法:遍历一次

image-20250318215619877

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        if (head == null || head.next == null || left == right) return head;
        ListNode cur = head, pre = null, next, partOneTail, partTwoTail;
        int k = 1;

        while (k < left) {
            pre = cur;
            cur = cur.next;
            k++;
        }
        partOneTail = pre;
        partTwoTail = cur;
        pre = cur;
        cur = cur.next;

        while (k < right) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
            k++;
        }
        if (partOneTail == null) {
            head = pre;
        }else {
            partOneTail.next = pre;
        }
        partTwoTail.next = cur;
        return head;      
    }
}

总结下思路:

  1. 定位反转区间的起点和前驱:
    • 使用指针 cur 和 pre,遍历到第 left 个节点。
    • pre 停在第 left-1 个节点(可能是 null,如果 left=1),cur 指向第 left 个节点。
    • 记录 partOneTail = pre(第一部分的尾节点)。
  2. 标记反转区间的起点:
    • partTwoTail = cur,即反转部分的原始起点(第 left 个节点),后续用来连接第三部分。
  3. 反转第 left 到 right 的部分:
    • 使用经典链表反转方法(prev, curr, next 指针法,也就是力扣206题的思路)。
    • 从第 left 到第 right 个节点,逐个调整 next 指针,使其指向前一个节点。
    • 反转后,pre 指向第 right 个节点(新头部),cur 指向第 right+1 个节点。
  4. 连接三部分:
    • 如果 partOneTail 是 null(即 left=1),新头节点是反转后的 pre。
    • 否则,partOneTail.next = pre,连接第一部分和反转后的第二部分。
    • partTwoTail.next = cur,连接反转后的第二部分和第三部分。
  5. 返回结果:
    • 返回 head(可能是原始头或新头)。

这个思路还是比较丝滑的,其中需要考虑partOneTail是不是空的问题(当left=1的时候就需要这么考虑)

反转双链表

1
2
3
4
5
6
7
8
9
10
11
public DoubleListNode reverse(DoubleListNode head) {
    DoubleListNode pre = null, next;
    while (head != null) {
        next = head.next;
        head.next = pre;
        head.pre = next;
        pre = head;
        head = next;
    }
    return pre;
}