#206
简单
高频
迭代法反转链表
这是一道围绕链表、递归展开的高频练习。建议先掌握「迭代法」这套写法,再结合下方步骤讲解理解状态维护、边界处理和复杂度取舍。
链表
递归
题目分析
给你一条单链表,要求把整条链表的指向完全反过来,并返回新的头节点。
比如原链表是 1 -> 2 -> 3 -> 4 -> 5,反转后就会变成 5 -> 4 -> 3 -> 2 -> 1。
一句话概括:
把链表中每个节点的 next 指针方向全部翻转过来。
接下来怎么看推荐代码: 带着这个理解再看推荐代码时,重点观察这条主线:用
prev、curr、next 三个指针边遍历边改方向,把每个节点的 next 指回前驱。推荐代码
推荐解法:迭代法
时间复杂度: O(n)
空间复杂度: O(1)
核心思路: 用
prev、curr、next 三个指针边遍历边改方向,把每个节点的 next 指回前驱。
/**
* 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 {
/**
* 迭代法反转链表
* 使用三个指针:prev、curr、next
*/
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;
}
}
结构化讲解
面试时怎么讲
开场思路
这题我会用迭代法。核心是三个指针:前驱、当前节点、以及提前保存的后继节点。
核心过程
- 先用
prev = null、curr = head初始化。 - 每轮先保存
next = curr.next,避免后面的链丢失。 - 然后执行
curr.next = prev完成当前节点的反转。 - 最后整体推进
prev = curr、curr = next,直到遍历结束。
复杂度总结
时间复杂度 O(n),空间复杂度 O(1)。
面试补一句:链表翻转真正要记住的是动作顺序,不是代码表面长什么样。
核心思路
迭代法的核心不是“背模板”,而是保证改指针时不丢链。只要坚持“先存后继,再改指向,最后整体前进”,链表就能稳定反转。
步骤讲解
1
初始化三个指针
让 prev = null,curr = head,准备从头节点开始逐个反转。
为什么这样做:反转后的新尾节点应该指向
null,所以 prev 一开始必须为空。对应代码提示:ListNode prev = null; ListNode curr = head;
2
先保存后继节点
在改 curr.next 之前,先用 next 暂存原来的后继节点。
为什么这样做:一旦你把当前节点的 next 改掉,原链表往后的入口就会消失。
对应代码提示:ListNode next = curr.next;
3
把当前节点反指向前驱
执行 curr.next = prev,让链表方向在当前节点处完成翻转。
为什么这样做:这一步才是真正的“反转”动作,其他操作都是为了让它安全发生。
对应代码提示:curr.next = prev;
4
整体向前推进
把 prev 移到当前节点,再把 curr 移到刚才保存的 next。
为什么这样做:处理完一个节点后,前半段已经反转完成,后半段继续按同样模板处理即可。
对应代码提示:prev = curr; curr = next;
5
循环结束返回新头节点
当 curr 走到 null 时,prev 正好停在反转后的新头节点。
为什么这样做:原链表最后一个节点会成为新链表头部。
对应代码提示:return prev;
易错点
没先保存 next 就改指针
这是链表反转最常见的错误。一旦先写 curr.next = prev,后面的链就找不回来了。
正确理解:严格保持顺序:先
next = curr.next,再改 curr.next。返回值写成 curr
循环结束时 curr 已经是 null,真正的新头节点是 prev。
正确理解:记住结束状态:
curr 出界,prev 停在新头。把 prev 初始成 head
这样会导致头节点先指向自己或形成错误连接,整个反转过程会混乱。
正确理解:
prev 必须从 null 开始,表示反转后尾节点指向空。复杂度与适用判断
时间复杂度:O(n)
空间复杂度:O(1)
比其他方案更好在哪里:比递归法更省空间,也更适合在面试白板上稳定写出。
代价是什么:代码可读性上没有递归法那么“顺着定义展开”。
适用判断:需要原地反转链表,且希望控制额外空间时,优先使用迭代法。
额外提醒
- 链表题里最重要的安全动作之一,就是先保存后继节点。
- 这道题是很多链表变形题的基础模板,比如局部翻转和 K 个一组翻转。
其他语言 / 其他解法
递归法
递归法把“反转前半段”的动作推迟到回溯阶段完成。它思路优雅,但需要额外的递归栈空间。
递归法
递归法把“反转前半段”的动作推迟到回溯阶段完成。它思路优雅,但需要额外的递归栈空间。
时间复杂度:O(n)
空间复杂度:O(n)
一句话思路:递归先走到链表尾部,再在回溯阶段把后一个节点反向指回当前节点。
/**
* 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 newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
动画演示
如果你还没完全看懂,可以展开动画演示。
迭代法动画演示
想看这段解法是怎么一步步运行的,可以展开动画继续观察。
迭代法动画演示
想看这段解法是怎么一步步运行的,可以展开动画继续观察。
准备就绪 - 输入链表值(用逗号分隔),然后点击开始
尚未开始
链表指针状态
1
2
3
4
5
伪代码定位
prev = null curr = head while (curr != null): next = curr.next curr.next = prev prev = curr curr = next return prev
递归法动画演示
想看这段解法是怎么一步步运行的,可以展开动画继续观察。
递归法动画演示
想看这段解法是怎么一步步运行的,可以展开动画继续观察。
准备就绪 - 输入链表值(用逗号分隔),然后点击开始
尚未开始
链表回溯状态
1
2
3
4
5
递归调用栈
栈为空
伪代码定位
reverse(head):
if head == null or head.next == null:
return head
newHead = reverse(head.next)
head.next.next = head
head.next = null
return newHead动画演示仅在桌面端提供,移动端请优先阅读推荐代码与结构化讲解。