92. 反转链表 II
✨核心逻辑
本题采用 虚拟头节点 + 头插法(局部反转) 的策略:
- 虚拟头节点(Dummy Node):因为
left有可能等于1(即从链表头开始反转),为了方便统一处理,我们创建一个虚拟头节点指向原head。 - 定位前驱节点:通过循环,先让指针走到
left位置的前一个节点(前驱节点)停下。 - 局部头插法反转:确认反转区间起点节点后,利用“头插法”,将
left后面的节点依次移动到前驱节点(即区间头部)的后面。 - 完成拼接:通过循环重复
right - left次头插操作,即可完成指定区间的链表反转。
🔥代码实现(含详细变量注释)
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
// l1:初始化一个虚拟头节点(值为0),并将其 next 指向原链表的头节点 head
ListNode l1 = new ListNode(0);
l1.next = head;
// w1:用于永久保存虚拟头节点 l1 的初始引用
// 因为 l1 后续会被移动,最后需要借助 w1.next 返回结果链表的真实头节点
ListNode w1 = l1;
// 步骤1:找到 left 位置的前一个节点
// 循环执行 left - 1 次,将 l1 指针移动到 left 前驱节点的位置
for (int i = 0; i < left - 1; i++) {
l1 = l1.next;
}
// left1:指向 left 位置的节点(反转区间的第一个节点)
// 在反转过程中,它的 next 会不断变化,反转结束后它将是区间的最后一个节点
ListNode left1 = l1.next;
// 步骤2:交换区间内的元素,采用头插法将后面节点逐个移动到前面
// 需要移动 right - left 次
for (int i = 0; i < right - left; i++) {
// hou:每次获取 left1 的 next 节点(即当前即将被移动到区间最前面的节点)
ListNode hou = left1.next;
// 核心头插操作:
// 1. 将 left1 的 next 跳过 hou,指向 hou 的下一个节点
left1.next = hou.next;
// 2. 将 hou 插入到 l1 节点(区间前驱)的后面
hou.next = l1.next;
// 3. 更新 l1 的 next 指向刚插入的 hou,保证头插法的连续性
l1.next = hou;
}
// 返回虚拟头节点的下一个节点,即反转完成后的链表头节点
return w1.next;
}
}
- ⏱️复杂度分析
时间复杂度:O(N),其中 N 是链表的长度。最坏情况下 left 为 1,right 为 N,我们只需遍历链表一次。
空间复杂度:O(1),仅使用了 l1、w1、left1、hou 和循环变量 i 等常数个额外指针变量。
🔍总结一下:
其中:如果需要反转整个链表,肯定需要left节点的前驱节点,这也就是虚拟节点的作用。如果没有用null去操作会出问题
第二个for循环:每次拿到left后的首节点,然后提取到反转区间d前面,以此类推,循环终止时则完成