加载笔记内容...
加载笔记内容...
链表反转作为数据结构基础操作中的经典问题,蕴含着指针操作、递归思维、空间优化等关键技术要点。本文将从底层原理出发,结合工程实践中的常见问题,深度解析两种主流实现方案。
单链表(Singly Linked List)由节点通过单向指针连接而成,每个节点包含:
1class ListNode {
2 val: number
3 next: ListNode | null
4}
内存分布特点:
反转操作本质是指针方向的重构,实际应用场景包括:
1function reverseList(head: ListNode | null): ListNode | null {
2 let prev = null;
3 let cur = head;
4 while (cur) {
5 const next = cur.next; // 临时存储
6 cur.next = prev; // 指针转向
7 prev = cur; // 前驱指针推进
8 cur = next; // 当前指针推进
9 }
10 return prev;
11}
时间复杂度:O(n)
空间复杂度:O(1)(仅使用三个指针变量)
初始状态:
prev: null
cur: 1 → 2 → 3 → ∅
迭代过程:
∅ ← 1
2 → 3 → ∅prev=1
, cur=2
∅ ← 1 ← 2
3 → ∅prev=2
, cur=3
∅ ← 1 ← 2 ← 3
prev=3
, cur=null
visited
集合判断可防止无限循环(需O(n)空间)递归本质是数学归纳法的程序表达:
head === null || head.next === null
时,链表无需反转head.next
之后的链表已反转,只需处理当前节点1function reverseList(head: ListNode | null): ListNode | null {
2 if (!head || !head.next) return head;
3 const newHead = reverseList(head.next);
4 head.next.next = head; // 逆向指针
5 head.next = null; // 防止循环引用
6 return newHead;
7}
以链表1 → 2 → 3 → ∅
为例:
1Call Stack | 当前head | 操作
2-----------------------------------------
3reverseList(3) | 3 | 返回3(base case)
4reverseList(2) | 2 | 2.next.next=2 → 3→2
5reverseList(1) | 1 | 1.next.next=1 → 2→1
空间复杂度:O(n)(调用栈深度)
当前实现不是尾递归形式,无法自动优化。可改造为:
1function reverseList(head: ListNode, prev = null) {
2 if (!head) return prev;
3 const next = head.next;
4 head.next = prev;
5 return reverseList(next, head);
6}
但TypeScript/JavaScript引擎普遍不支持TCO(Tail Call Optimization),实际仍可能栈溢出。
维度 | 迭代法 | 递归法 |
---|---|---|
空间复杂度 | O(1) | O(n) |
时间复杂度 | O(n) | O(n) |
可读性 | 直观 | 抽象 |
适用场景 | 长链表 | 短链表/函数式编程 |
扩展性 | 容易实现变种 | 修改成本较高 |
工程选型建议:
1function reverseBetween(head: ListNode | null, left: number, right: number): ListNode | null {
2 // 实现思路:定位区间前后节点,进行子链表反转
3}
关键技术点:哨兵节点、区间边界处理
1function reverseKGroup(head: ListNode | null, k: number): ListNode | null {
2 // 递归或迭代实现分组反转
3}
常见陷阱:剩余节点不足k个时的处理
在反转前加入检测逻辑:
1function hasCycle(head: ListNode | null): boolean {
2 let slow = head, fast = head;
3 while (fast && fast.next) {
4 slow = slow.next;
5 fast = fast.next.next;
6 if (slow === fast) return true;
7 }
8 return false;
9}
完整测试矩阵应包含:
1const testCases = [
2 { input: null, expected: null }, // 空链表
3 { input: [1], expected: [1] }, // 单节点
4 { input: [1,2], expected: [2,1] }, // 双节点
5 { input: [1,2,3,4,5], expected: [5,4,3,2,1] }, // 多节点
6 { input: createCycleList(), expected: 'Error' } // 循环链表
7];
反转链表问题犹如数据结构的"Hello World",其背后蕴含着指针操作的精髓与递归思维的奥妙。理解这个问题的不同解法,不仅能提升算法能力,更能培养对内存模型和程序执行的深层认知。