文章目录
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
- 需要将链表中所有节点的指针方向反转
- 返回新的头节点(原来的尾节点)
关键点:
- 链表的方向需要完全颠倒
- 原来的头节点将变成尾节点,原来的尾节点将变成头节点
- 每个节点的
next
指针都要改变方向 - 需要处理空链表和单节点链表的特殊情况
2.1 反转前后对比
反转前:
1 -> 2 -> 3 -> 4 -> 5 -> NULL
反转后:
NULL <- 1 <- 2 <- 3 <- 4 <- 5
实际上就是:
5 -> 4 -> 3 -> 2 -> 1 -> NULL
2.2 核心思路
反转链表的关键在于:
- 改变指针方向:将每个节点的
next
指针从指向后一个节点改为指向前一个节点 - 保存节点信息:在改变指针之前,必须先保存下一个节点的信息,否则会丢失链表的后续部分
- 逐步处理:从头到尾遍历链表,逐个处理每个节点
3. 解法一:迭代法(三指针法)
3.1 算法思路
迭代法是最直观、最容易理解的方法。我们使用三个指针:
prev
:指向当前节点的前一个节点(反转后的下一个节点)curr
:指向当前正在处理的节点next
:临时保存当前节点的下一个节点,防止丢失
核心步骤:
- 初始化
prev = null
,curr = head
- 循环处理每个节点:
- 保存
curr.next
到next
- 将
curr.next
指向prev
(反转指针) - 将
prev
和curr
都向前移动一步
- 保存
- 返回
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 -> NULL
prev 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 -> NULL
prev 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; // 前一个节点,初始为null
ListNode 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: null
curr: 1
next: 2
保存下一个节点:next = 2
反转指针:1.next = null
移动指针:
prev = 1
curr = 2
当前已反转部分:已反转:... -> 1 -> null | 待处理:[2 -> 3 -> 4 -> 5 -> null]
--- 第2步 ---
当前状态:
prev: 1
curr: 2
next: 3
保存下一个节点:next = 3
反转指针:2.next = 1
移动指针:
prev = 2
curr = 3
当前已反转部分:已反转:... -> 2 -> null | 待处理:[3 -> 4 -> 5 -> null]
--- 第3步 ---
当前状态:
prev: 2
curr: 3
next: 4
保存下一个节点:next = 4
反转指针:3.next = 2
移动指针:
prev = 3
curr = 4
当前已反转部分:已反转:... -> 3 -> null | 待处理:[4 -> 5 -> null]
--- 第4步 ---
当前状态:
prev: 3
curr: 4
next: 5
保存下一个节点:next = 5
反转指针:4.next = 3
移动指针:
prev = 4
curr = 5
当前已反转部分:已反转:... -> 4 -> null | 待处理:[5 -> null]
--- 第5步 ---
当前状态:
prev: 4
curr: 5
next: null
保存下一个节点:next = null
反转指针:5.next = 4
移动指针:
prev = 5
curr = 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; // 移动prev
curr = next; // 移动curr
}
return prev; // prev指向新的头节点
}
}
3.7 复杂度分析
时间复杂度: O(n)
- 其中 n 是链表的长度
- 我们需要遍历链表一次,每个节点只访问一次
- 每次操作都是常数时间
空间复杂度: O(1)
- 只使用了常数级的额外空间(三个指针变量)
- 不依赖于输入链表的长度
3.8 适用场景
迭代法是解决链表反转问题的首选方法,因为:
- 空间效率高:只使用常数级额外空间
- 易于理解:逻辑直观,容易掌握
- 性能优秀:没有递归调用开销
- 适用性广:适合处理长链表,不会出现栈溢出问题
4. 解法二:递归法
4.1 递归思路
递归的核心思想是将大问题分解为小问题:
- 递归假设:假设
reverseList(head.next)
能够正确反转从第二个节点开始的子链表 - 处理当前节点:将当前节点接到已反转的子链表末尾
- 返回新头节点:返回反转后链表的头节点
递归过程分析:
对于链表 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->5
4.next.next = 4; // 5.next = 4
4.next = null; // 断开 4->5
// 结果:5->4 (4指向null)
return 5;
}
// 处理节点3
// 当前状态:5->4 3->4
3.next.next = 3; // 4.next = 3
3.next = null; // 断开 3->4
// 结果:5->4->3 (3指向null)
return 5;
}
// 处理节点2
// 当前状态:5->4->3 2->3
2.next.next = 2; // 3.next = 2
2.next = null; // 断开 2->3
// 结果:5->4->3->2 (2指向null)
return 5;
}
// 处理节点1
// 当前状态:5->4->3->2 1->2
1.next.next = 1; // 2.next = 1
1.next = null; // 断开 1->2
// 结果:5->4->3->2->1 (1指向null)
return 5;
}
最终结果:5 -> 4 -> 3 -> 2 -> 1 -> null
4.6 递归算法的关键理解
-
递归假设的重要性:
- 我们假设
reverseList(head.next)
能正确反转子链表 - 基于这个假设,我们只需要处理当前节点与子链表的连接
- 我们假设
-
"反转"的具体操作:
head.next.next = head; // 让下一个节点指向当前节点 head.next = null; // 断开当前节点到下一个节点的连接
-
返回值的传递:
- 新的头节点(原链表的尾节点)在整个递归过程中保持不变
- 每层递归都返回这个相同的头节点
4.7 复杂度分析
时间复杂度: O(n)
- 每个节点被访问一次,总共 n 次递归调用
- 每次递归的操作都是常数时间
空间复杂度: O(n)
- 递归调用栈的深度为 n(链表长度)
- 每层递归需要常数级的额外空间
- 总空间复杂度为 O(n)
4.8 递归法的优缺点
优点:
- 代码简洁:递归版本的代码更加简洁优雅
- 思路清晰:递归思维符合问题的分解特性
- 易于理解:一旦理解递归思想,代码逻辑很清晰
缺点:
- 空间开销大:需要 O(n) 的递归栈空间
- 可能栈溢出:对于很长的链表,可能导致栈溢出
- 性能略差:函数调用有一定开销
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.next
return 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 {
@Test
public void testEmptyList() {
Solution sol = new Solution();
ListNode result = sol.reverseList(null);
assertNull("空链表应返回null", result);
}
@Test
public 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);
}
@Test
public 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);
}
@Test
public 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
和两个整数 left
和 right
,其中 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;
}
@Override
public 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 核心要点总结
-
算法理解:
- 反转链表本质是改变指针方向
- 关键是保存下一个节点的信息,避免链表断裂
- 迭代法使用三指针,递归法利用调用栈
-
实现技巧:
- 边界条件:空链表和单节点链表
- 迭代法:
prev
、curr
、next
三指针配合 - 递归法:明确递归终止条件和回溯处理
-
复杂度权衡:
- 迭代法:时间O(n),空间O(1),推荐使用
- 递归法:时间O(n),空间O(n),适合理解递归思想
10.2 学习收获
通过学习反转链表问题,你应该掌握:
- 链表操作的基本技巧
- 指针操作的细节处理
- 迭代与递归两种思维方式
- 边界条件的重要性
- 算法分析和优化方法
10.3 面试准备建议
-
熟练掌握基本实现:
- 能够快速写出迭代法的标准实现
- 理解递归法的思想和实现
- 处理好各种边界条件
-
扩展知识:
- 了解反转链表的变种题目
- 掌握相关的链表操作技巧
- 理解实际应用场景
-
调试能力:
- 能够快速定位和修复常见错误
- 掌握链表问题的调试技巧
- 具备代码review能力
10.4 进阶学习方向
-
更多链表题目:
- 合并两个有序链表
- 环形链表检测
- 链表排序
- 复杂链表的复制
-
高级数据结构:
- 双向链表
- 跳跃表
- 链表与其他数据结构的结合
-
算法设计模式:
- 双指针技巧
- 递归设计模式
- 分治算法思想
反转链表是链表操作的经典问题,掌握它不仅能帮你解决相关题目,更重要的是培养处理链表问题的思维方式和编程技巧。持续练习和思考,你将在链表相关的算法问题上游刃有余!