#92
中等
高频
迭代法反转链表 II
这是一道围绕链表展开的高频练习。建议先掌握「迭代法」这套写法,再结合下方步骤讲解理解状态维护、边界处理和复杂度取舍。
链表
题目分析
给你一条链表,以及两个位置 left 和 right。
要求只反转从第 left 个节点到第 right 个节点这一段,其他部分保持不变。
一句话概括:
找到待翻转区间的前驱和后继,只对中间那段做链表反转。
接下来怎么看推荐代码: 带着这个理解再看推荐代码时,重点观察这条主线:先走到待反转区间前一个节点,再用头插法把区间内节点逐个翻到前面。
推荐代码
推荐解法:迭代法
时间复杂度: O(n)
空间复杂度: O(1)
核心思路: 先走到待反转区间前一个节点,再用头插法把区间内节点逐个翻到前面。
/**
* 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 reverseBetween(ListNode head, int left, int right) {
/*
先找到左节点
再找到右节点
反转左节点开始的链表
左节点next赋值为最开始右节点的next,左节点的上一个节点的next赋值为右节点
*/
// 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode pre = dummyNode;
// 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
// 建议写在 for 循环里,语义清晰
for (int i = 0; i < left - 1; i++) {
pre = pre.next;
}
// 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
ListNode rightNode = pre;
for (int i = 0; i < right - left + 1; i++) {
rightNode = rightNode.next;
}
// 第 3 步:切断出一个子链表(截取链表)
ListNode leftNode = pre.next;
ListNode curr = rightNode.next;
// 注意:切断链接
pre.next = null;
rightNode.next = null;
// 第 4 步:同第 206 题,反转链表的子区间
reverseList(leftNode);
// 第 5 步:接回到原来的链表中
pre.next = rightNode;
leftNode.next = curr;
return dummyNode.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;
}
}结构化讲解
面试时怎么讲
开场思路
这题我会用链表头插法原地翻转指定区间。
核心过程
- 先用
dummy和pre找到待翻转区间前驱。 - 让
curr指向区间第一个节点。 - 重复把
curr.next摘下来,头插到pre后面。 - 循环
right-left次后,区间翻转完成。
复杂度总结
时间复杂度 O(n),空间复杂度 O(1)。
面试补一句:这题最容易掉链子的地方,是头插时指针更新顺序。
核心思路
局部翻转链表的关键不是整段拆下来,而是固定区间前驱,让后面的节点一个个头插到前面,逐步把 [left, right] 区间反过来。
步骤讲解
1
先走到反转区间前驱节点
借助 dummy 找到位置 left 前一个节点 pre。
为什么这样做:后续所有头插操作都要围绕这个前驱节点展开。
对应代码提示:for (int i = 1; i < left; i++) pre = pre.next;
2
把区间内节点逐个头插到 pre 后面
设 curr 指向区间第一个节点,每次把 curr.next 摘下来插到 pre 后面。
为什么这样做:这样无需额外空间,就能原地把局部顺序反过来。
对应代码提示:ListNode next = curr.next; curr.next = next.next; next.next = pre.next; pre.next = next;
3
重复 right-left 次完成局部翻转
头插操作重复固定次数后,区间内节点顺序就完全翻转好了。
为什么这样做:每次操作都把一个后继节点搬到最前面,逐步完成局部逆序。
对应代码提示:for (int i = 0; i < right - left; i++) { ... }
易错点
没加 dummy 导致 left=1 时难处理
当翻转从头节点开始时,没有统一前驱会让代码分叉很多。
正确理解:先补一个
dummy 节点统一边界。把整段链表断开后难以接回
这样容易丢指针,调试成本也高。
正确理解:优先用头插法在原链表内部原地完成翻转。
循环次数写错
局部翻转只需要搬运 right-left 次,不是区间长度次。
正确理解:严格按
right - left 次做头插。复杂度与适用判断
时间复杂度:O(n)
空间复杂度:O(1)
比其他方案更好在哪里:比先复制值或借栈反转更省空间,也和链表题常见原地修改要求一致。
适用判断:链表题只要要求原地翻转一段连续区间,头插法是高频模板。
额外提醒
- 局部翻转真正固定不动的是区间前驱
pre和区间起点curr。
动画演示
如果你还没完全看懂,可以展开动画演示。
迭代法动画演示
想看这段解法是怎么一步步运行的,可以展开动画继续观察。
迭代法动画演示
想看这段解法是怎么一步步运行的,可以展开动画继续观察。
准备就绪 - 输入链表值、左边界和右边界,然后点击开始
动画演示仅在桌面端提供,移动端请优先阅读推荐代码与结构化讲解。