反转链表的头插法解析
反转链表代码超详细解析(头插法思路)
这段代码是反转单链表的 迭代最优解(时间复杂度O(n)、空间复杂度O(1)),核心思路是用「头插法」把原链表的节点逐个"搬到"新链表的头部,最终新链表的头就是反转后的结果。下面从「核心逻辑→逐行拆解→步骤演示→关键细节」帮你彻底吃透。
一、先明确两个核心概念
1. 单链表的结构
每个 ListNode 节点包含两部分:
-
val:节点存储的值; -
next:指针(引用),指向后一个节点(链表末尾节点的next为null)。
原链表示例:1 → 2 → 3 → 4 → null(head 指向节点1),反转目标:null ← 1 ← 2 ← 3 ← 4(新头指向节点4)。
2. 算法核心思路(头插法)
把原链表的节点 从前往后逐个取出,每次都插入到「新链表的头部」,相当于反向构建新链表:
-
用
pre记录「新链表的头节点」(初始为空,因为新链表还没节点); -
用
cur记录「原链表当前要处理的节点」(初始为原链表头head); -
用
nxt临时保存「原链表的下一个节点」(防止处理当前节点时,原链表"断开"); -
循环执行:取当前节点→插入新链表头部→移动指针,直到原链表遍历完。
二、代码逐行拆解(带注释)
Java
三、分步演示(用示例链表 1→2→3→4→null)
我们用表格跟踪每一步 pre、cur、nxt 的值和链表结构变化,直观理解反转过程。
初始状态(循环开始前)
| 指针 | 指向节点 | 链表结构(原链表) | 新链表结构 |
|---|---|---|---|
pre |
null |
1 → 2 → 3 → 4 → null |
null |
cur |
1 | ||
nxt |
未定义 |
第一次循环(处理节点1)
-
ListNode nxt = cur.next;→nxt = 2(保存原链表下一个节点); -
cur.next = pre;→ 节点1的next指向pre(null),此时节点1:1→null; -
pre = cur;→pre = 1(新链表头更新为节点1,新链表:1→null); -
cur = nxt;→cur = 2(原链表当前节点向后移)。
| 指针 | 指向节点 | 原链表剩余部分 | 新链表结构 |
|---|---|---|---|
pre |
1 | 2 → 3 → 4 → null |
1 → null |
cur |
2 | ||
nxt |
2 |
第二次循环(处理节点2)
-
nxt = cur.next;→nxt = 3(保存节点2的下一个节点); -
cur.next = pre;→ 节点2的next指向pre(1),此时节点2:2→1; -
pre = cur;→pre = 2(新链表头更新为节点2,新链表:2→1→null); -
cur = nxt;→cur = 3。
| 指针 | 指向节点 | 原链表剩余部分 | 新链表结构 |
|---|---|---|---|
pre |
2 | 3 → 4 → null |
2 → 1 → null |
cur |
3 | ||
nxt |
3 |
第三次循环(处理节点3)
-
nxt = cur.next;→nxt = 4; -
cur.next = pre;→ 节点3的next指向2,节点3:3→2; -
pre = cur;→pre = 3(新链表:3→2→1→null); -
cur = nxt;→cur = 4。
| 指针 | 指向节点 | 原链表剩余部分 | 新链表结构 |
|---|---|---|---|
pre |
3 | 4 → null |
3 → 2 → 1 → null |
cur |
4 | ||
nxt |
4 |
第四次循环(处理节点4)
-
nxt = cur.next;→nxt = null(节点4是原链表最后一个节点,下一个为null); -
cur.next = pre;→ 节点4的next指向3,节点4:4→3; -
pre = cur;→pre = 4(新链表:4→3→2→1→null); -
cur = nxt;→cur = null(原链表遍历结束,循环终止)。
| 指针 | 指向节点 | 原链表剩余部分 | 新链表结构 |
|---|---|---|---|
pre |
4 | null |
4 → 3 → 2 → 1 → null |
cur |
null |
||
nxt |
null |
循环结束,返回pre
此时pre指向新链表的头节点4,反转后的链表为 4→3→2→1→null,完全符合预期。
四、关键细节与避坑点
1. 为什么必须用nxt保存cur.next?
如果不保存,执行 cur.next = pre 后,cur.next 就指向了新链表(而非原链表的下一个节点),后续无法再找到原链表的剩余节点,导致链表"断开",遍历终止。nxt 相当于"临时备忘录",记录原链表的后续路径。
2. 循环终止条件为什么是cur != null?
cur 是原链表的"当前处理节点":
-
当
cur不为null时,说明还有节点没处理; -
当
cur为null时,原链表所有节点都已插入新链表,循环结束。
3. 为什么返回pre而不是cur?
循环结束时,cur 已经指向null(原链表遍历完),而 pre 指向的是最后一个处理的节点(原链表的尾节点,新链表的头节点),所以返回pre才是反转后的链表头。
4. 边界情况是否覆盖?
-
空链表(
head = null):cur初始为null,循环不执行,返回pre = null,正确; -
单节点链表(
head = 1→null):循环执行1次,pre最终指向1,返回1,正确。
五、总结
这段代码的核心是「头插法迭代反转」:
-
用三个指针(
pre、cur、nxt)分工明确:pre管新链表,cur管原链表当前节点,nxt管原链表后续节点; -
每一步都围绕"取节点→反转指针→移动指针",逻辑闭环;
-
时间复杂度O(n)(遍历一次链表),空间复杂度O(1)(仅用三个指针,不额外占用空间),是最优解法。
记住一句话:"保存下一个,反转当前,移动双指针",就能轻松默写这段代码!如果还是不理解,可以动手画一画每一步的指针变化,可视化后会瞬间清晰。