反转链表 II 图解题解
这道题到底在问什么
- 输入
- list=1→2→3→4→5→6→7→8, left=2, right=9
- 输出
- 1→9→8→7→6→5→4→3→2→10
最优解:一步一步想明白
- 3记住三个角色:prev(锚点·不动)、cur(区间头·会沉底)、nxt(每轮被头插的那个)。
- 4prev 从头节点 1 起步,要走到第 1 个节点(区间 [2,9] 的前一个)当锚点。
- 5prev 停在 1(区间前驱,整段头插期间它纹丝不动)。cur 指区间第一个 2——它最终会沉到区间末尾。准备头插。
- 6第 1 轮:cur=2 后面的 3 就是这轮要头插的 nxt。先记住它,准备从链上摘下来插到 prev=1 之后。
- 7把 3 从当前位置摘下来(它左边那根箭头先断成 ·)。因为已经用 nxt 记住了它,断了也不丢。
- 83 头插到 prev=1 正后方(箭头掉成 ←,变绿表示已就位)。它现在是区间已翻部分的新头。 cur 仍是 2,继续摘下一个。
- 9第 2 轮:cur=2 后面的 4 就是这轮要头插的 nxt。先记住它,准备从链上摘下来插到 prev=1 之后。
- 10把 4 从当前位置摘下来(它左边那根箭头先断成 ·)。因为已经用 nxt 记住了它,断了也不丢。
- 114 头插到 prev=1 正后方(箭头掉成 ←,变绿表示已就位)。它现在是区间已翻部分的新头。 cur 仍是 2,继续摘下一个。
- 12第 3 轮:cur=2 后面的 5 就是这轮要头插的 nxt。先记住它,准备从链上摘下来插到 prev=1 之后。
- 13把 5 从当前位置摘下来(它左边那根箭头先断成 ·)。因为已经用 nxt 记住了它,断了也不丢。
- 145 头插到 prev=1 正后方(箭头掉成 ←,变绿表示已就位)。它现在是区间已翻部分的新头。 cur 仍是 2,继续摘下一个。
- 15第 4 轮:cur=2 后面的 6 就是这轮要头插的 nxt。先记住它,准备从链上摘下来插到 prev=1 之后。
- 16把 6 从当前位置摘下来(它左边那根箭头先断成 ·)。因为已经用 nxt 记住了它,断了也不丢。
- 176 头插到 prev=1 正后方(箭头掉成 ←,变绿表示已就位)。它现在是区间已翻部分的新头。 cur 仍是 2,继续摘下一个。
- 18第 5 轮:cur=2 后面的 7 就是这轮要头插的 nxt。先记住它,准备从链上摘下来插到 prev=1 之后。
- 19把 7 从当前位置摘下来(它左边那根箭头先断成 ·)。因为已经用 nxt 记住了它,断了也不丢。
- 207 头插到 prev=1 正后方(箭头掉成 ←,变绿表示已就位)。它现在是区间已翻部分的新头。 cur 仍是 2,继续摘下一个。
- 21第 6 轮:cur=2 后面的 8 就是这轮要头插的 nxt。先记住它,准备从链上摘下来插到 prev=1 之后。
- 22把 8 从当前位置摘下来(它左边那根箭头先断成 ·)。因为已经用 nxt 记住了它,断了也不丢。
- 238 头插到 prev=1 正后方(箭头掉成 ←,变绿表示已就位)。它现在是区间已翻部分的新头。 cur 仍是 2,继续摘下一个。
- 24第 7 轮:cur=2 后面的 9 就是这轮要头插的 nxt。先记住它,准备从链上摘下来插到 prev=1 之后。
- 25把 9 从当前位置摘下来(它左边那根箭头先断成 ·)。因为已经用 nxt 记住了它,断了也不丢。
- 269 头插到 prev=1 正后方(箭头掉成 ←,变绿表示已就位)。它现在是区间已翻部分的新头。 区间内最后一个也翻好了。
- 27区间 [2,9] 整段翻好:9→8→7→6→5→4→3→2。区间外的 1 和 10 一根箭头都没动。整条变成 1→9→8→7→6→5→4→3→2→10,返回头节点 1。
⚠️ 容易写错的地方
✗ 错:不用 dummy,单独特判 left==1
✓ 对:加 dummy 哨兵,prev 统一从 dummy 出发
left==1 时 head 前面没有真实前驱,加 dummy 充当虚拟前驱,不用写两套逻辑
✗ 错:头插写成 cur.next=prev.next 顺序乱
✓ 对:严守 nxt=cur.next → cur.next=nxt.next → nxt.next=prev.next → prev.next=nxt
四步有依赖,换序会断链或成环
✗ 错:cur 每轮跟着移动
✓ 对:cur 始终是区间第一个节点(沉底者),整段只 prev 不变、cur 不变
cur 移动会找错「下一个要摘的 nxt」
完整代码(Python / C++ / Java)
Python
def reverseBetween(head, left, right):
dummy = ListNode(0, head)
prev = dummy
for _ in range(left - 1): # ① 走到区间前驱
prev = prev.next
cur = prev.next # 区间第一个(会沉底)
for _ in range(right - left): # ② 头插 right-left 次
nxt = cur.next
cur.next = nxt.next # 把 nxt 摘下
nxt.next = prev.next # nxt 插到 prev 之后
prev.next = nxt
return dummy.nextC++
ListNode* reverseBetween(ListNode* head,int left,int right){
ListNode dummy(0); dummy.next = head;
ListNode* prev = &dummy;
for(int i=0;i<left-1;i++) prev = prev->next;
ListNode* cur = prev->next;
for(int i=0;i<right-left;i++){
ListNode* nxt = cur->next;
cur->next = nxt->next;
nxt->next = prev->next;
prev->next = nxt;
}
return dummy.next;
}Java
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy = new ListNode(0, head);
ListNode prev = dummy;
for (int i = 0; i < left - 1; i++) prev = prev.next;
ListNode cur = prev.next;
for (int i = 0; i < right - left; i++) {
ListNode nxt = cur.next;
cur.next = nxt.next;
nxt.next = prev.next;
prev.next = nxt;
}
return dummy.next;
}
}复杂度
时间
O(n)
走到前驱 O(left),头插 O(right-left),合计一遍线性
空间
O(1)
只用 dummy/prev/cur/nxt 几个指针,原地反转
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 反转链表 II 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么用 dummy 哨兵?+
left==1 时区间从 head 开始,head 前面没有真实节点、prev 无处可站;加 dummy 让 prev 永远落在虚拟前驱上,省掉特判、代码统一。
和「先断出子链单独反转再接回」相比?+
那种做法要记 4 个边界指针、反转后再缝合,易错且多扫一遍。头插法一趟原地完成,prev 不动,只摘 nxt 头插,更稳。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 反转链表 II 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。