当前位置: 首页 > news >正文

Java详解LeetCode 热题 100(23):LeetCode 206. 反转链表(Reverse Linked List)详解

文章目录

    • 1. 题目描述
      • 1.1 链表节点定义
    • 2. 理解题目
      • 2.1 反转前后对比
      • 2.2 核心思路
    • 3. 解法一:迭代法(三指针法)
      • 3.1 算法思路
      • 3.2 详细图解
      • 3.3 Java代码实现
      • 3.4 代码执行过程演示
      • 3.5 执行结果示例
      • 3.6 优化版本(简化代码)
      • 3.7 复杂度分析
      • 3.8 适用场景
    • 4. 解法二:递归法
      • 4.1 递归思路
      • 4.2 Java递归实现
      • 4.3 递归过程详细演示
      • 4.4 递归执行过程
      • 4.5 递归的图解说明
      • 4.6 递归算法的关键理解
      • 4.7 复杂度分析
      • 4.8 递归法的优缺点
    • 5. 完整测试用例和边界处理
      • 5.1 完整测试代码
    • 6. 常见错误与调试技巧
      • 6.1 常见错误分析
        • 错误1:忘记保存下一个节点
        • 错误2:返回错误的头节点
        • 错误3:边界条件处理不当
        • 错误4:递归中形成环
      • 6.2 调试技巧
        • 技巧1:添加调试输出
        • 技巧2:单元测试验证
        • 技巧3:可视化工具
    • 7. 扩展题目与变种
      • 7.1 反转链表 II (LeetCode 92)
      • 7.2 两两交换链表中的节点 (LeetCode 24)
      • 7.3 K个一组翻转链表 (LeetCode 25)
      • 7.4 判断链表是否为回文 (LeetCode 234)
    • 8. 实际应用场景
      • 8.1 浏览器历史记录
      • 8.2 音乐播放器
      • 8.3 文档编辑器的撤销功能
    • 9. 性能分析与优化
      • 9.1 时间复杂度对比
      • 9.2 性能测试代码
    • 10. 总结与学习建议
      • 10.1 核心要点总结
      • 10.2 学习收获
      • 10.3 面试准备建议
      • 10.4 进阶学习方向

1. 题目描述

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

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

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

1.1 链表节点定义

/*** 单链表节点的定义*/
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; }
}

2. 理解题目

这道题要求我们反转一个单链表。具体来说:

  • 输入是一个链表的头节点 head
  • 需要将链表中所有节点的指针方向反转
  • 返回新的头节点(原来的尾节点)

关键点:

  1. 链表的方向需要完全颠倒
  2. 原来的头节点将变成尾节点,原来的尾节点将变成头节点
  3. 每个节点的 next 指针都要改变方向
  4. 需要处理空链表和单节点链表的特殊情况

2.1 反转前后对比

反转前:

1 -> 2 -> 3 -> 4 -> 5 -> NULL

反转后:

NULL <- 1 <- 2 <- 3 <- 4 <- 5

实际上就是:

5 -> 4 -> 3 -> 2 -> 1 -> NULL

2.2 核心思路

反转链表的关键在于:

  1. 改变指针方向:将每个节点的 next 指针从指向后一个节点改为指向前一个节点
  2. 保存节点信息:在改变指针之前,必须先保存下一个节点的信息,否则会丢失链表的后续部分
  3. 逐步处理:从头到尾遍历链表,逐个处理每个节点

3. 解法一:迭代法(三指针法)

3.1 算法思路

迭代法是最直观、最容易理解的方法。我们使用三个指针:

  • prev:指向当前节点的前一个节点(反转后的下一个节点)
  • curr:指向当前正在处理的节点
  • next:临时保存当前节点的下一个节点,防止丢失

核心步骤:

  1. 初始化 prev = nullcurr = head
  2. 循环处理每个节点:
    • 保存 curr.nextnext
    • curr.next 指向 prev(反转指针)
    • prevcurr 都向前移动一步
  3. 返回 prev(新的头节点)

3.2 详细图解

让我们通过图解来理解每一步:

初始状态:

prev = NULL
curr = 1 -> 2 -> 3 -> 4 -> 5 -> NULL
next = ?

第一步:处理节点1

1. next = curr.next = 2 -> 3 -> 4 -> 5 -> NULL  (保存下一个节点)
2. curr.next = prev = NULL                       (反转指针)
3. prev = curr = 1                               (移动prev)
4. curr = next = 2 -> 3 -> 4 -> 5 -> NULL       (移动curr)结果:NULL <- 1    2 -> 3 -> 4 -> 5 -> NULLprev  curr

第二步:处理节点2

1. next = curr.next = 3 -> 4 -> 5 -> NULL
2. curr.next = prev = 1
3. prev = curr = 2
4. curr = next = 3 -> 4 -> 5 -> NULL结果:NULL <- 1 <- 2    3 -> 4 -> 5 -> NULLprev  curr

继续这个过程直到 curr 为 NULL…

3.3 Java代码实现

/*** 迭代法反转链表(三指针法)* 时间复杂度:O(n),其中 n 是链表的长度,需要遍历链表一次* 空间复杂度:O(1),只使用常数级额外空间*/
class Solution {public ListNode reverseList(ListNode head) {// 边界条件:空链表或只有一个节点if (head == null || head.next == null) {return head;}// 初始化三个指针ListNode prev = null;    // 前一个节点,初始为nullListNode curr = head;    // 当前节点,从头节点开始ListNode next = null;    // 下一个节点,用于临时保存// 遍历链表,逐个反转节点while (curr != null) {// 步骤1:保存下一个节点,防止丢失链表后续部分next = curr.next;// 步骤2:反转当前节点的指针,指向前一个节点curr.next = prev;// 步骤3:移动指针,为下一次迭代做准备prev = curr;         // prev移动到当前节点curr = next;         // curr移动到下一个节点}// 循环结束时,prev指向原链表的最后一个节点,即新链表的头节点return prev;}
}

3.4 代码执行过程演示

让我们用示例 [1,2,3,4,5] 来详细演示代码执行过程:

/*** 迭代过程详细演示类*/
public class IterativeReverseDemo {public ListNode reverseList(ListNode head) {System.out.println("=== 开始反转链表 ===");System.out.println("原链表:" + printList(head));if (head == null || head.next == null) {System.out.println("链表为空或只有一个节点,直接返回");return head;}ListNode prev = null;ListNode curr = head;ListNode next = null;int step = 1;while (curr != null) {System.out.println("\n--- 第" + step + "步 ---");System.out.println("当前状态:");System.out.println("  prev: " + (prev == null ? "null" : prev.val));System.out.println("  curr: " + curr.val);System.out.println("  next: " + (curr.next == null ? "null" : curr.next.val));// 保存下一个节点next = curr.next;System.out.println("保存下一个节点:next = " + (next == null ? "null" : next.val));// 反转指针curr.next = prev;System.out.println("反转指针:" + curr.val + ".next = " + (prev == null ? "null" : prev.val));// 移动指针prev = curr;curr = next;System.out.println("移动指针:");System.out.println("  prev = " + prev.val);System.out.println("  curr = " + (curr == null ? "null" : curr.val));// 打印当前已反转部分System.out.println("当前已反转部分:" + printReversedPart(prev, curr));step++;}System.out.println("\n=== 反转完成 ===");System.out.println("最终结果:" + printList(prev));return prev;}// 辅助方法:打印链表private String printList(ListNode head) {if (head == null) return "[]";StringBuilder sb = new StringBuilder();sb.append("[");while (head != null) {sb.append(head.val);if (head.next != null) sb.append(" -> ");head = head.next;}sb.append(" -> null]");return sb.toString();}// 辅助方法:打印已反转部分private String printReversedPart(ListNode reversed, ListNode remaining) {StringBuilder sb = new StringBuilder();sb.append("已反转:");if (reversed == null) {sb.append("null");} else {// 需要从前往后打印已反转部分,但我们只有指向最后一个的指针// 为了演示,我们简化显示sb.append("... -> ").append(reversed.val).append(" -> null");}sb.append(" | 待处理:");if (remaining == null) {sb.append("null");} else {ListNode temp = remaining;sb.append("[");while (temp != null) {sb.append(temp.val);if (temp.next != null) sb.append(" -> ");temp = temp.next;}sb.append(" -> null]");}return sb.toString();}
}

3.5 执行结果示例

=== 开始反转链表 ===
原链表:[1 -> 2 -> 3 -> 4 -> 5 -> null]--- 第1步 ---
当前状态:prev: nullcurr: 1next: 2
保存下一个节点:next = 2
反转指针:1.next = null
移动指针:prev = 1curr = 2
当前已反转部分:已反转:... -> 1 -> null | 待处理:[2 -> 3 -> 4 -> 5 -> null]--- 第2步 ---
当前状态:prev: 1curr: 2next: 3
保存下一个节点:next = 3
反转指针:2.next = 1
移动指针:prev = 2curr = 3
当前已反转部分:已反转:... -> 2 -> null | 待处理:[3 -> 4 -> 5 -> null]--- 第3步 ---
当前状态:prev: 2curr: 3next: 4
保存下一个节点:next = 4
反转指针:3.next = 2
移动指针:prev = 3curr = 4
当前已反转部分:已反转:... -> 3 -> null | 待处理:[4 -> 5 -> null]--- 第4步 ---
当前状态:prev: 3curr: 4next: 5
保存下一个节点:next = 5
反转指针:4.next = 3
移动指针:prev = 4curr = 5
当前已反转部分:已反转:... -> 4 -> null | 待处理:[5 -> null]--- 第5步 ---
当前状态:prev: 4curr: 5next: null
保存下一个节点:next = null
反转指针:5.next = 4
移动指针:prev = 5curr = null
当前已反转部分:已反转:... -> 5 -> null | 待处理:null=== 反转完成 ===
最终结果:[5 -> 4 -> 3 -> 2 -> 1 -> null]

3.6 优化版本(简化代码)

/*** 迭代法的简洁版本* 功能完全相同,但代码更简洁*/
class SolutionOptimized {public ListNode reverseList(ListNode head) {ListNode prev = null;ListNode curr = head;while (curr != null) {ListNode next = curr.next;  // 保存下一个节点curr.next = prev;           // 反转指针prev = curr;                // 移动prevcurr = next;                // 移动curr}return prev;  // prev指向新的头节点}
}

3.7 复杂度分析

时间复杂度: O(n)

  • 其中 n 是链表的长度
  • 我们需要遍历链表一次,每个节点只访问一次
  • 每次操作都是常数时间

空间复杂度: O(1)

  • 只使用了常数级的额外空间(三个指针变量)
  • 不依赖于输入链表的长度

3.8 适用场景

迭代法是解决链表反转问题的首选方法,因为:

  1. 空间效率高:只使用常数级额外空间
  2. 易于理解:逻辑直观,容易掌握
  3. 性能优秀:没有递归调用开销
  4. 适用性广:适合处理长链表,不会出现栈溢出问题

4. 解法二:递归法

4.1 递归思路

递归的核心思想是将大问题分解为小问题:

  1. 递归假设:假设 reverseList(head.next) 能够正确反转从第二个节点开始的子链表
  2. 处理当前节点:将当前节点接到已反转的子链表末尾
  3. 返回新头节点:返回反转后链表的头节点

递归过程分析:
对于链表 1 -> 2 -> 3 -> 4 -> 5 -> null

reverseList(1->2->3->4->5)
├── 递归处理子链表:reverseList(2->3->4->5) 返回 5->4->3->2
├── 将当前节点1接到反转后的链表末尾:5->4->3->2->1
└── 返回新的头节点5最终结果:5->4->3->2->1->null

4.2 Java递归实现

/*** 递归法反转链表* 时间复杂度:O(n),其中 n 是链表的长度* 空间复杂度:O(n),递归调用栈的深度为 n*/
class RecursiveSolution {public ListNode reverseList(ListNode head) {// 递归终止条件:空链表或单节点链表if (head == null || head.next == null) {return head;}// 递归反转子链表,获得新的头节点ListNode newHead = reverseList(head.next);// 反转当前节点和下一个节点的连接// head.next 现在指向子链表的第一个节点(反转前的第二个节点)// 我们让这个节点指向当前节点head.next.next = head;// 断开当前节点到下一个节点的连接,避免形成环head.next = null;// 返回新的头节点(整个递归过程中保持不变)return newHead;}
}

4.3 递归过程详细演示

/*** 递归过程可视化演示*/
public class RecursiveReverseDemo {private int depth = 0;  // 递归深度计数器public ListNode reverseList(ListNode head) {String indent = "  ".repeat(depth);  // 缩进显示递归层次System.out.println(indent + "→ 进入递归 depth=" + depth + ", 当前节点: " + (head == null ? "null" : head.val));// 递归终止条件if (head == null || head.next == null) {System.out.println(indent + "← 递归边界,返回: " + (head == null ? "null" : head.val));return head;}System.out.println(indent + "当前处理: " + head.val + " -> " + (head.next == null ? "null" : head.next.val));depth++;  // 递归深度增加// 递归处理子链表System.out.println(indent + "递归处理子链表...");ListNode newHead = reverseList(head.next);depth--;  // 递归深度减少System.out.println(indent + "← 递归返回,继续处理节点: " + head.val);System.out.println(indent + "子链表已反转,新头节点: " + newHead.val);// 反转当前节点和下一个节点的连接System.out.println(indent + "反转连接: " + head.val + " <-> " + head.next.val);System.out.println(indent + "执行: " + head.next.val + ".next = " + head.val);head.next.next = head;System.out.println(indent + "断开: " + head.val + ".next = null");head.next = null;System.out.println(indent + "当前层处理完成,返回头节点: " + newHead.val);return newHead;}
}

4.4 递归执行过程

对于输入 [1,2,3,4,5],递归过程如下:

→ 进入递归 depth=0, 当前节点: 1
当前处理: 1 -> 2
递归处理子链表...→ 进入递归 depth=1, 当前节点: 2当前处理: 2 -> 3递归处理子链表...→ 进入递归 depth=2, 当前节点: 3当前处理: 3 -> 4递归处理子链表...→ 进入递归 depth=3, 当前节点: 4当前处理: 4 -> 5递归处理子链表...→ 进入递归 depth=4, 当前节点: 5← 递归边界,返回: 5← 递归返回,继续处理节点: 4子链表已反转,新头节点: 5反转连接: 4 <-> 5执行: 5.next = 4断开: 4.next = null当前层处理完成,返回头节点: 5← 递归返回,继续处理节点: 3子链表已反转,新头节点: 5反转连接: 3 <-> 4执行: 4.next = 3断开: 3.next = null当前层处理完成,返回头节点: 5← 递归返回,继续处理节点: 2子链表已反转,新头节点: 5反转连接: 2 <-> 3执行: 3.next = 2断开: 2.next = null当前层处理完成,返回头节点: 5
← 递归返回,继续处理节点: 1
子链表已反转,新头节点: 5
反转连接: 1 <-> 2
执行: 2.next = 1
断开: 1.next = null
当前层处理完成,返回头节点: 5

4.5 递归的图解说明

原链表:1 -> 2 -> 3 -> 4 -> 5 -> null递归调用栈展开:
reverseList(1->2->3->4->5) {newHead = reverseList(2->3->4->5) {newHead = reverseList(3->4->5) {newHead = reverseList(4->5) {newHead = reverseList(5) {return 5;  // 递归出口}// 处理节点4// 当前状态:5  4->54.next.next = 4;  // 5.next = 44.next = null;    // 断开 4->5// 结果:5->4  (4指向null)return 5;}// 处理节点3// 当前状态:5->4  3->43.next.next = 3;  // 4.next = 33.next = null;    // 断开 3->4// 结果:5->4->3  (3指向null)return 5;}// 处理节点2// 当前状态:5->4->3  2->32.next.next = 2;  // 3.next = 22.next = null;    // 断开 2->3// 结果:5->4->3->2  (2指向null)return 5;}// 处理节点1// 当前状态:5->4->3->2  1->21.next.next = 1;  // 2.next = 11.next = null;    // 断开 1->2// 结果:5->4->3->2->1  (1指向null)return 5;
}最终结果:5 -> 4 -> 3 -> 2 -> 1 -> null

4.6 递归算法的关键理解

  1. 递归假设的重要性

    • 我们假设 reverseList(head.next) 能正确反转子链表
    • 基于这个假设,我们只需要处理当前节点与子链表的连接
  2. "反转"的具体操作

    head.next.next = head;  // 让下一个节点指向当前节点
    head.next = null;       // 断开当前节点到下一个节点的连接
    
  3. 返回值的传递

    • 新的头节点(原链表的尾节点)在整个递归过程中保持不变
    • 每层递归都返回这个相同的头节点

4.7 复杂度分析

时间复杂度: O(n)

  • 每个节点被访问一次,总共 n 次递归调用
  • 每次递归的操作都是常数时间

空间复杂度: O(n)

  • 递归调用栈的深度为 n(链表长度)
  • 每层递归需要常数级的额外空间
  • 总空间复杂度为 O(n)

4.8 递归法的优缺点

优点:

  1. 代码简洁:递归版本的代码更加简洁优雅
  2. 思路清晰:递归思维符合问题的分解特性
  3. 易于理解:一旦理解递归思想,代码逻辑很清晰

缺点:

  1. 空间开销大:需要 O(n) 的递归栈空间
  2. 可能栈溢出:对于很长的链表,可能导致栈溢出
  3. 性能略差:函数调用有一定开销

5. 完整测试用例和边界处理

5.1 完整测试代码

/*** 反转链表完整测试类*/
public class ReverseListTest {public static void main(String[] args) {ReverseListTest test = new ReverseListTest();System.out.println("=== 反转链表测试套件 ===\n");// 运行所有测试用例test.testCase1_NormalList();test.testCase2_EmptyList();test.testCase3_SingleNode();test.testCase4_TwoNodes();test.testCase5_LargeList();test.testCase6_PerformanceTest();System.out.println("=== 所有测试完成 ===");}/*** 测试用例1:正常链表 [1,2,3,4,5]*/public void testCase1_NormalList() {System.out.println("【测试用例1】正常链表 [1,2,3,4,5]");// 构建测试链表ListNode head = buildList(new int[]{1, 2, 3, 4, 5});System.out.println("原链表:" + printList(head));// 测试迭代法Solution iterativeSol = new Solution();ListNode result1 = iterativeSol.reverseList(copyList(head));System.out.println("迭代法结果:" + printList(result1));// 测试递归法RecursiveSolution recursiveSol = new RecursiveSolution();ListNode result2 = recursiveSol.reverseList(copyList(head));System.out.println("递归法结果:" + printList(result2));// 验证结果boolean isCorrect = isEqual(result1, result2) && isListEqual(result1, new int[]{5, 4, 3, 2, 1});System.out.println("结果验证:" + (isCorrect ? "✅ 通过" : "❌ 失败"));System.out.println();}/*** 测试用例2:空链表 []*/public void testCase2_EmptyList() {System.out.println("【测试用例2】空链表 []");ListNode head = null;System.out.println("原链表:" + printList(head));Solution iterativeSol = new Solution();ListNode result1 = iterativeSol.reverseList(head);System.out.println("迭代法结果:" + printList(result1));RecursiveSolution recursiveSol = new RecursiveSolution();ListNode result2 = recursiveSol.reverseList(head);System.out.println("递归法结果:" + printList(result2));boolean isCorrect = (result1 == null && result2 == null);System.out.println("结果验证:" + (isCorrect ? "✅ 通过" : "❌ 失败"));System.out.println();}/*** 测试用例3:单节点链表 [42]*/public void testCase3_SingleNode() {System.out.println("【测试用例3】单节点链表 [42]");ListNode head = new ListNode(42);System.out.println("原链表:" + printList(head));Solution iterativeSol = new Solution();ListNode result1 = iterativeSol.reverseList(copyList(head));System.out.println("迭代法结果:" + printList(result1));RecursiveSolution recursiveSol = new RecursiveSolution();ListNode result2 = recursiveSol.reverseList(copyList(head));System.out.println("递归法结果:" + printList(result2));boolean isCorrect = isEqual(result1, result2) && result1 != null && result1.val == 42 && result1.next == null;System.out.println("结果验证:" + (isCorrect ? "✅ 通过" : "❌ 失败"));System.out.println();}/*** 测试用例4:两个节点 [1,2]*/public void testCase4_TwoNodes() {System.out.println("【测试用例4】两个节点 [1,2]");ListNode head = buildList(new int[]{1, 2});System.out.println("原链表:" + printList(head));Solution iterativeSol = new Solution();ListNode result1 = iterativeSol.reverseList(copyList(head));System.out.println("迭代法结果:" + printList(result1));RecursiveSolution recursiveSol = new RecursiveSolution();ListNode result2 = recursiveSol.reverseList(copyList(head));System.out.println("递归法结果:" + printList(result2));boolean isCorrect = isEqual(result1, result2) && isListEqual(result1, new int[]{2, 1});System.out.println("结果验证:" + (isCorrect ? "✅ 通过" : "❌ 失败"));System.out.println();}/*** 测试用例5:较大链表 [1,2,3,...,10]*/public void testCase5_LargeList() {System.out.println("【测试用例5】较大链表 [1,2,3,4,5,6,7,8,9,10]");int[] values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};ListNode head = buildList(values);System.out.println("原链表:" + printList(head));Solution iterativeSol = new Solution();ListNode result1 = iterativeSol.reverseList(copyList(head));System.out.println("迭代法结果:" + printList(result1));RecursiveSolution recursiveSol = new RecursiveSolution();ListNode result2 = recursiveSol.reverseList(copyList(head));System.out.println("递归法结果:" + printList(result2));int[] expected = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};boolean isCorrect = isEqual(result1, result2) && isListEqual(result1, expected);System.out.println("结果验证:" + (isCorrect ? "✅ 通过" : "❌ 失败"));System.out.println();}/*** 测试用例6:性能测试*/public void testCase6_PerformanceTest() {System.out.println("【测试用例6】性能测试 - 1000个节点");// 构建1000个节点的链表int[] values = new int[1000];for (int i = 0; i < 1000; i++) {values[i] = i + 1;}ListNode head = buildList(values);System.out.println("构建了包含1000个节点的链表");// 测试迭代法性能long start1 = System.nanoTime();Solution iterativeSol = new Solution();ListNode result1 = iterativeSol.reverseList(copyList(head));long end1 = System.nanoTime();long iterativeTime = end1 - start1;// 测试递归法性能long start2 = System.nanoTime();RecursiveSolution recursiveSol = new RecursiveSolution();ListNode result2 = recursiveSol.reverseList(copyList(head));long end2 = System.nanoTime();long recursiveTime = end2 - start2;System.out.println("迭代法耗时:" + iterativeTime + " 纳秒");System.out.println("递归法耗时:" + recursiveTime + " 纳秒");System.out.println("性能对比:递归法耗时是迭代法的 " + String.format("%.2f", (double) recursiveTime / iterativeTime) + " 倍");// 验证结果正确性boolean isCorrect = isEqual(result1, result2);System.out.println("结果验证:" + (isCorrect ? "✅ 通过" : "❌ 失败"));System.out.println();}// ===== 辅助方法 =====/*** 根据数组构建链表*/private ListNode buildList(int[] values) {if (values == null || values.length == 0) {return null;}ListNode head = new ListNode(values[0]);ListNode curr = head;for (int i = 1; i < values.length; i++) {curr.next = new ListNode(values[i]);curr = curr.next;}return head;}/*** 打印链表*/private String printList(ListNode head) {if (head == null) return "[]";StringBuilder sb = new StringBuilder();sb.append("[");ListNode curr = head;while (curr != null) {sb.append(curr.val);if (curr.next != null) sb.append(", ");curr = curr.next;}sb.append("]");return sb.toString();}/*** 复制链表*/private ListNode copyList(ListNode head) {if (head == null) return null;ListNode newHead = new ListNode(head.val);ListNode newCurr = newHead;ListNode curr = head.next;while (curr != null) {newCurr.next = new ListNode(curr.val);newCurr = newCurr.next;curr = curr.next;}return newHead;}/*** 比较两个链表是否相等*/private boolean isEqual(ListNode l1, ListNode l2) {while (l1 != null && l2 != null) {if (l1.val != l2.val) return false;l1 = l1.next;l2 = l2.next;}return l1 == null && l2 == null;}/*** 检查链表是否与给定数组相等*/private boolean isListEqual(ListNode head, int[] expected) {ListNode curr = head;for (int i = 0; i < expected.length; i++) {if (curr == null || curr.val != expected[i]) {return false;}curr = curr.next;}return curr == null;}
}

6. 常见错误与调试技巧

6.1 常见错误分析

错误1:忘记保存下一个节点
// ❌ 错误代码:会导致链表丢失
public ListNode reverseList(ListNode head) {ListNode prev = null;ListNode curr = head;while (curr != null) {curr.next = prev;    // 直接修改next,丢失了后续节点!prev = curr;curr = curr.next;    // curr.next已经被修改,无法继续遍历}return prev;
}// ✅ 正确代码:先保存再修改
public ListNode reverseList(ListNode head) {ListNode prev = null;ListNode curr = head;while (curr != null) {ListNode next = curr.next;  // 先保存下一个节点curr.next = prev;           // 再修改指针prev = curr;curr = next;                // 使用保存的节点继续遍历}return prev;
}
错误2:返回错误的头节点
// ❌ 错误:返回原头节点
public ListNode reverseList(ListNode head) {ListNode prev = null;ListNode curr = head;while (curr != null) {ListNode next = curr.next;curr.next = prev;prev = curr;curr = next;}return head;  // 错误!head现在是尾节点,应该返回prev
}// ✅ 正确:返回新的头节点
public ListNode reverseList(ListNode head) {// ... 相同的处理逻辑 ...return prev;  // prev指向新的头节点(原来的尾节点)
}
错误3:边界条件处理不当
// ❌ 错误:没有处理特殊情况
public ListNode reverseList(ListNode head) {ListNode prev = null;ListNode curr = head;// 如果head为null,这里会出错while (curr != null) {// ...}return prev;
}// ✅ 正确:先处理边界条件
public ListNode reverseList(ListNode head) {if (head == null || head.next == null) {return head;  // 空链表或单节点直接返回}// ... 正常处理逻辑 ...
}
错误4:递归中形成环
// ❌ 错误:没有断开原连接,形成环
public ListNode reverseList(ListNode head) {if (head == null || head.next == null) {return head;}ListNode newHead = reverseList(head.next);head.next.next = head;  // 反转连接// 忘记断开原连接!会形成环:head <-> head.nextreturn newHead;
}// ✅ 正确:断开原连接
public ListNode reverseList(ListNode head) {if (head == null || head.next == null) {return head;}ListNode newHead = reverseList(head.next);head.next.next = head;head.next = null;       // 必须断开原连接return newHead;
}

6.2 调试技巧

技巧1:添加调试输出
/*** 带调试输出的反转函数*/
public ListNode reverseListWithDebug(ListNode head) {System.out.println("开始反转:" + printList(head));if (head == null || head.next == null) {System.out.println("边界情况,直接返回");return head;}ListNode prev = null;ListNode curr = head;int step = 0;while (curr != null) {System.out.println("步骤 " + (++step) + ":");System.out.println("  处理节点: " + curr.val);System.out.println("  当前状态: prev=" + (prev == null ? "null" : prev.val) + ", curr=" + curr.val);ListNode next = curr.next;curr.next = prev;prev = curr;curr = next;System.out.println("  操作后: prev=" + prev.val + ", curr=" + (curr == null ? "null" : curr.val));System.out.println("  当前已反转部分: " + printPartialList(prev, 3));System.out.println();}System.out.println("反转完成:" + printList(prev));return prev;
}private String printPartialList(ListNode head, int maxNodes) {if (head == null) return "null";StringBuilder sb = new StringBuilder();ListNode curr = head;int count = 0;while (curr != null && count < maxNodes) {sb.append(curr.val);if (curr.next != null && count < maxNodes - 1) sb.append("->");curr = curr.next;count++;}if (curr != null) sb.append("->...");return sb.toString();
}
技巧2:单元测试验证
/*** 单元测试类*/
public class ReverseListUnitTest {@Testpublic void testEmptyList() {Solution sol = new Solution();ListNode result = sol.reverseList(null);assertNull("空链表应返回null", result);}@Testpublic void testSingleNode() {Solution sol = new Solution();ListNode head = new ListNode(1);ListNode result = sol.reverseList(head);assertNotNull("单节点不应为null", result);assertEquals("值应该是1", 1, result.val);assertNull("下一个节点应为null", result.next);}@Testpublic void testTwoNodes() {Solution sol = new Solution();ListNode head = new ListNode(1);head.next = new ListNode(2);ListNode result = sol.reverseList(head);assertEquals("第一个节点值应为2", 2, result.val);assertEquals("第二个节点值应为1", 1, result.next.val);assertNull("第三个节点应为null", result.next.next);}@Testpublic void testNormalList() {Solution sol = new Solution();ListNode head = buildList(new int[]{1, 2, 3, 4, 5});ListNode result = sol.reverseList(head);int[] expected = {5, 4, 3, 2, 1};for (int i = 0; i < expected.length; i++) {assertNotNull("节点不应为null", result);assertEquals("节点值不匹配", expected[i], result.val);result = result.next;}assertNull("最后应为null", result);}
}
技巧3:可视化工具
/*** 链表可视化工具*/
public class ListVisualizer {/*** 生成链表的图形表示*/public static void visualizeList(ListNode head, String title) {System.out.println("\n=== " + title + " ===");if (head == null) {System.out.println("null");return;}// 打印节点值ListNode curr = head;while (curr != null) {System.out.print("┌─────┐");if (curr.next != null) System.out.print("    ");curr = curr.next;}System.out.println();// 打印节点内容curr = head;while (curr != null) {System.out.printf("│  %2d │", curr.val);if (curr.next != null) System.out.print(" -> ");curr = curr.next;}System.out.println(" -> null");// 打印底部边框curr = head;while (curr != null) {System.out.print("└─────┘");if (curr.next != null) System.out.print("    ");curr = curr.next;}System.out.println("\n");}/*** 演示反转过程*/public static void demonstrateReverse() {ListNode head = buildList(new int[]{1, 2, 3, 4, 5});visualizeList(head, "原始链表");Solution sol = new Solution();ListNode reversed = sol.reverseList(head);visualizeList(reversed, "反转后链表");}
}

7. 扩展题目与变种

7.1 反转链表 II (LeetCode 92)

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

/*** 反转链表的指定区间*/
public class ReverseListII {public ListNode reverseBetween(ListNode head, int left, int right) {if (head == null || left == right) {return head;}// 创建虚拟头节点ListNode dummy = new ListNode(0);dummy.next = head;// 找到left位置的前一个节点ListNode prevLeft = dummy;for (int i = 1; i < left; i++) {prevLeft = prevLeft.next;}// 反转left到right之间的节点ListNode prev = null;ListNode curr = prevLeft.next;for (int i = left; i <= right; i++) {ListNode next = curr.next;curr.next = prev;prev = curr;curr = next;}// 重新连接prevLeft.next.next = curr;  // 连接反转部分的尾部prevLeft.next = prev;       // 连接反转部分的头部return dummy.next;}
}

7.2 两两交换链表中的节点 (LeetCode 24)

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

/*** 两两交换链表节点*/
public class SwapPairs {/*** 迭代法*/public ListNode swapPairs(ListNode head) {ListNode dummy = new ListNode(0);dummy.next = head;ListNode prev = dummy;while (prev.next != null && prev.next.next != null) {// 要交换的两个节点ListNode first = prev.next;ListNode second = prev.next.next;// 交换prev.next = second;first.next = second.next;second.next = first;// 移动prev到下一组的前面prev = first;}return dummy.next;}/*** 递归法*/public ListNode swapPairsRecursive(ListNode head) {if (head == null || head.next == null) {return head;}ListNode second = head.next;head.next = swapPairsRecursive(second.next);second.next = head;return second;}
}

7.3 K个一组翻转链表 (LeetCode 25)

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

/*** K个一组翻转链表*/
public class ReverseKGroup {public ListNode reverseKGroup(ListNode head, int k) {if (head == null || k == 1) {return head;}// 检查是否有k个节点ListNode curr = head;for (int i = 0; i < k; i++) {if (curr == null) {return head;  // 不足k个节点,不反转}curr = curr.next;}// 反转前k个节点ListNode prev = null;curr = head;for (int i = 0; i < k; i++) {ListNode next = curr.next;curr.next = prev;prev = curr;curr = next;}// 递归处理剩余节点head.next = reverseKGroup(curr, k);return prev;}
}

7.4 判断链表是否为回文 (LeetCode 234)

/*** 判断链表是否为回文* 思路:找到中点,反转后半部分,然后比较*/
public class PalindromeLinkedList {public boolean isPalindrome(ListNode head) {if (head == null || head.next == null) {return true;}// 找到中点ListNode slow = head;ListNode fast = head;while (fast.next != null && fast.next.next != null) {slow = slow.next;fast = fast.next.next;}// 反转后半部分ListNode secondHalf = reverseList(slow.next);// 比较前半部分和后半部分ListNode firstHalf = head;while (secondHalf != null) {if (firstHalf.val != secondHalf.val) {return false;}firstHalf = firstHalf.next;secondHalf = secondHalf.next;}return true;}private ListNode reverseList(ListNode head) {ListNode prev = null;ListNode curr = head;while (curr != null) {ListNode next = curr.next;curr.next = prev;prev = curr;curr = next;}return prev;}
}

8. 实际应用场景

8.1 浏览器历史记录

/*** 浏览器历史记录实现* 使用链表反转来实现前进后退功能*/
public class BrowserHistory {private ListNode current;static class HistoryNode extends ListNode {String url;HistoryNode prev;HistoryNode next;HistoryNode(String url) {this.url = url;}}public BrowserHistory(String homepage) {current = new HistoryNode(homepage);}public void visit(String url) {// 清除当前节点之后的历史current.next = null;// 添加新页面HistoryNode newPage = new HistoryNode(url);current.next = newPage;newPage.prev = current;current = newPage;}public String back(int steps) {while (steps > 0 && current.prev != null) {current = current.prev;steps--;}return current.url;}public String forward(int steps) {while (steps > 0 && current.next != null) {current = current.next;steps--;}return current.url;}
}

8.2 音乐播放器

/*** 音乐播放器的播放列表* 支持反转播放顺序*/
public class MusicPlayer {private ListNode playlist;private ListNode current;private boolean reversed = false;static class Song extends ListNode {String title;String artist;Song(String title, String artist) {this.title = title;this.artist = artist;}@Overridepublic String toString() {return title + " - " + artist;}}public void addSong(String title, String artist) {Song newSong = new Song(title, artist);if (playlist == null) {playlist = newSong;current = newSong;} else {ListNode tail = playlist;while (tail.next != null) {tail = tail.next;}tail.next = newSong;}}public void reversePlaylist() {playlist = reverseList(playlist);reversed = !reversed;System.out.println("播放列表已" + (reversed ? "反转" : "恢复"));}public Song getCurrentSong() {return (Song) current;}public Song nextSong() {if (current != null && current.next != null) {current = current.next;}return (Song) current;}private ListNode reverseList(ListNode head) {ListNode prev = null;ListNode curr = head;while (curr != null) {ListNode next = curr.next;curr.next = prev;prev = curr;curr = next;}return prev;}
}

8.3 文档编辑器的撤销功能

/*** 文档编辑器的撤销/重做功能* 使用链表反转实现操作历史管理*/
public class DocumentEditor {private ListNode operationHistory;private ListNode currentState;static class Operation extends ListNode {String type;String content;int position;Operation(String type, String content, int position) {this.type = type;this.content = content;this.position = position;}}public void executeOperation(String type, String content, int position) {Operation op = new Operation(type, content, position);// 清除当前状态之后的历史if (currentState != null) {currentState.next = null;}// 添加新操作if (operationHistory == null) {operationHistory = op;currentState = op;} else {currentState.next = op;currentState = op;}System.out.println("执行操作: " + type + " - " + content);}public void undo() {if (currentState != null) {System.out.println("撤销操作: " + currentState.type);// 找到前一个操作if (currentState == operationHistory) {currentState = null;} else {ListNode prev = operationHistory;while (prev.next != currentState) {prev = prev.next;}currentState = prev;}}}public void redo() {if (currentState == null && operationHistory != null) {currentState = operationHistory;System.out.println("重做操作: " + currentState.type);} else if (currentState != null && currentState.next != null) {currentState = currentState.next;System.out.println("重做操作: " + currentState.type);}}
}

9. 性能分析与优化

9.1 时间复杂度对比

方法时间复杂度空间复杂度优点缺点
迭代法O(n)O(1)空间效率高,性能稳定代码稍长
递归法O(n)O(n)代码简洁,思路清晰可能栈溢出

9.2 性能测试代码

/*** 性能测试类*/
public class PerformanceTest {public static void comparePerformance() {int[] sizes = {100, 1000, 10000, 100000};System.out.println("链表大小\t迭代法(ns)\t递归法(ns)\t性能比");System.out.println("================================================");for (int size : sizes) {ListNode list1 = createLargeList(size);ListNode list2 = copyList(list1);// 测试迭代法long start1 = System.nanoTime();new Solution().reverseList(list1);long end1 = System.nanoTime();long iterativeTime = end1 - start1;// 测试递归法long start2 = System.nanoTime();try {new RecursiveSolution().reverseList(list2);long end2 = System.nanoTime();long recursiveTime = end2 - start2;double ratio = (double) recursiveTime / iterativeTime;System.out.printf("%d\t\t%d\t\t%d\t\t%.2f\n", size, iterativeTime, recursiveTime, ratio);} catch (StackOverflowError e) {System.out.printf("%d\t\t%d\t\t栈溢出\t\t-\n", size, iterativeTime);}}}private static ListNode createLargeList(int size) {if (size <= 0) return null;ListNode head = new ListNode(1);ListNode curr = head;for (int i = 2; i <= size; i++) {curr.next = new ListNode(i);curr = curr.next;}return head;}private static ListNode copyList(ListNode head) {if (head == null) return null;ListNode newHead = new ListNode(head.val);ListNode newCurr = newHead;ListNode curr = head.next;while (curr != null) {newCurr.next = new ListNode(curr.val);newCurr = newCurr.next;curr = curr.next;}return newHead;}
}

10. 总结与学习建议

10.1 核心要点总结

  1. 算法理解

    • 反转链表本质是改变指针方向
    • 关键是保存下一个节点的信息,避免链表断裂
    • 迭代法使用三指针,递归法利用调用栈
  2. 实现技巧

    • 边界条件:空链表和单节点链表
    • 迭代法:prevcurrnext 三指针配合
    • 递归法:明确递归终止条件和回溯处理
  3. 复杂度权衡

    • 迭代法:时间O(n),空间O(1),推荐使用
    • 递归法:时间O(n),空间O(n),适合理解递归思想

10.2 学习收获

通过学习反转链表问题,你应该掌握:

  • 链表操作的基本技巧
  • 指针操作的细节处理
  • 迭代与递归两种思维方式
  • 边界条件的重要性
  • 算法分析和优化方法

10.3 面试准备建议

  1. 熟练掌握基本实现

    • 能够快速写出迭代法的标准实现
    • 理解递归法的思想和实现
    • 处理好各种边界条件
  2. 扩展知识

    • 了解反转链表的变种题目
    • 掌握相关的链表操作技巧
    • 理解实际应用场景
  3. 调试能力

    • 能够快速定位和修复常见错误
    • 掌握链表问题的调试技巧
    • 具备代码review能力

10.4 进阶学习方向

  1. 更多链表题目

    • 合并两个有序链表
    • 环形链表检测
    • 链表排序
    • 复杂链表的复制
  2. 高级数据结构

    • 双向链表
    • 跳跃表
    • 链表与其他数据结构的结合
  3. 算法设计模式

    • 双指针技巧
    • 递归设计模式
    • 分治算法思想

反转链表是链表操作的经典问题,掌握它不仅能帮你解决相关题目,更重要的是培养处理链表问题的思维方式和编程技巧。持续练习和思考,你将在链表相关的算法问题上游刃有余!

相关文章:

  • 机器学习:支持向量机(SVM)原理解析及垃圾邮件过滤实战
  • mac电脑安装 nvm 报错如何解决
  • 前端自动化测试利器:Playwright 全面介绍
  • Python-120:摇骰子的胜利概率
  • 23. Merge k Sorted Lists
  • 鸿蒙进阶——Mindspore Lite AI框架源码解读之模型加载详解(一)
  • DAY41 CNN
  • DAY 41 简单CNN
  • Python----目标检测(训练YOLOV8网络)
  • SpringBoot手动实现流式输出方案整理以及SSE规范输出详解
  • JavaSE知识总结(集合篇) ~个人笔记以及不断思考~持续更新
  • 学习经验分享【40】目标检测热力图制作
  • [HTML5]快速掌握canvas
  • (Python网络爬虫);抓取B站404页面小漫画
  • 智慧零工平台前端开发实战:从uni-app到跨平台应用
  • uniapp路由跳转toolbar页面
  • 通俗易懂解析:@ComponentScan 与 @MapperScan 的异同与用法
  • Java连接Redis和基础操作命令
  • 微软markitdown PDF/WORD/HTML文档转Markdown格式软件整合包下载
  • GODOT引擎学习日志
  • 51ppt模板网官网/邯郸seo排名
  • 宣城有做网站的公司吗/广告投放平台有哪些
  • 哈尔滨网站制作方案/免费培训机构
  • 心得网站建设/seo云优化方法
  • 网站建设运营部部长岗位职责/2345网址导航下载
  • 网站内容更新教程/济南网络seo公司