LeetCode 92. 反转链表II :题解与思路解析

反转链表是链表类题目中的基础题型,但 LeetCode 92 题「反转链表II」并非完整反转整个链表,而是反转链表中指定区间 [left, right] 的节点,这道题更考验对链表指针操作的精细化控制,也是面试中高频出现的变形题。今天就带大家一步步拆解这道题,从题意理解到代码实现,再到易错点规避,帮大家彻底搞懂。

一、题目大意

给定一个单链表的头节点 head,以及两个整数 left 和 right(满足 left ≤ right),要求只反转链表中「从第 left 个节点到第 right 个节点」的部分,反转后保持链表其余部分的顺序不变,最终返回修改后的链表头节点。

举个简单例子帮助理解:

  • 输入:head = [1,2,3,4,5], left = 2, right = 4

  • 反转区间 [2,4] 的节点(即 2→3→4 反转为 4→3→2)

  • 输出:[1,4,3,2,5]

二、核心难点分析

这道题的难点不在于「反转」本身(完整反转链表的双指针法大家基本都能掌握),而在于「局部反转」的边界处理:

  1. 如何精准定位到 left 节点的前驱节点 (记为 leftPreNode)和 right 节点的后继节点(记为 temp)?这两个节点是连接「未反转部分」和「反转部分」的关键,一旦处理不好就会出现链表断裂。

  2. 反转区间内的节点时,如何保证指针迭代不混乱?反转过程中 prev、curr 指针的移动的顺序,直接影响反转是否正确。

  3. 边界场景的处理:比如 left = 1(反转从表头开始)、left = right(无需反转)、链表长度等于 right(反转到表尾)等情况。

三、解题思路(迭代双指针法,最易理解)

针对局部反转的特点,我们采用「先定位边界,再反转区间,最后连接边界」的三步走策略,同时使用「虚拟头节点」规避表头反转的特殊处理,具体步骤如下:

步骤1:创建虚拟头节点(dummy node)

为什么要创建虚拟头节点?因为如果 left = 1,反转的是从表头开始的部分,此时我们需要一个前驱节点来连接反转后的新表头。虚拟头节点 dummy 的 val 可以设为 0,next 指向原表头 head,这样无论 left 是否为 1,我们都能统一处理 left 的前驱节点。

步骤2:定位边界节点

使用两个指针 prev 和 curr,从虚拟头节点开始迭代,找到以下三个关键节点:

  • leftPreNode:第 left 个节点的前驱节点(反转后需要它连接反转区间的新表头);

  • reverseStart:第 left 个节点(反转区间的原表头,反转后会变成区间的表尾);

  • curr 最终移动到第 right 个节点(反转区间的原表尾,反转后会变成区间的新表头)。

步骤3:反转 [left, right] 区间内的节点

这一步和「完整反转链表」的双指针逻辑一致:用 temp 暂存 curr 的下一个节点,然后让 curr 指向 prev(实现反转),再依次移动 prev 和 curr 指针,直到 curr 超出反转区间。

步骤4:连接边界,修复链表

反转完成后,需要将反转区间与原链表的其余部分重新连接:

  • 反转区间的原表头(reverseStart),现在要指向 right 节点的后继节点(temp);

  • left 的前驱节点(leftPreNode),现在要指向反转区间的新表头(原 right 节点)。

步骤5:返回结果

最终返回 dummy.next 即可(因为 dummy 是虚拟头节点,其 next 才是修改后链表的真实表头)。

四、完整代码实现(TypeScript)

结合上面的思路,附上完整可运行的 TypeScript 代码,每一行都添加了详细注释,新手也能轻松看懂:

typescript 复制代码
// 链表节点类定义(题目已给出,此处复用)
class ListNode {
  val: number
  next: ListNode | null
  constructor(val?: number, next?: ListNode | null) {
    this.val = (val === undefined ? 0 : val) // 节点值默认0
    this.next = (next === undefined ? null : next) // next指针默认null
  }
}

/**
 * 反转链表中从left到right的节点
 * @param head 链表头节点
 * @param left 反转起始位置(从1开始计数)
 * @param right 反转结束位置(从1开始计数)
 * @returns 反转后的链表头节点
 */
function reverseBetween(head: ListNode | null, left: number, right: number): ListNode | null {
  // 1. 创建虚拟头节点,避免left=1时的特殊处理
  const dummy = new ListNode(0, head);
  let curr = head; // 当前遍历的节点
  let prev: ListNode | null = dummy; // 当前节点的前驱节点(初始指向虚拟头)
  let i = 1; // 计数,标记当前节点是第几个(从1开始,和题目位置一致)
  
  let leftPreNode: ListNode | null = null; // left节点的前驱节点
  let reverseStart: ListNode | null = null; // left节点(反转区间的原表头)

  // 2. 遍历链表,定位边界节点并反转区间
  while (curr) {
    if (i < left || i > right) {
      // 情况1:当前节点不在反转区间,直接移动指针
      prev = curr;
      curr = curr.next;
    } else if (i === left) {
      // 情况2:找到反转起始位置left,记录关键节点
      leftPreNode = prev; // 保存left的前驱
      reverseStart = curr; // 保存反转区间的原表头
      // 移动指针,准备开始反转
      prev = curr;
      curr = curr.next;
    } else if (i === right) {
      // 情况3:找到反转结束位置right,处理反转的最后一步
      const temp: ListNode | null = curr.next; // 暂存right的后继节点(避免断裂)
      curr.next = prev; // 反转当前节点的指针
      
      // 连接反转区间与原链表
      if (reverseStart) reverseStart.next = temp; // 原表头指向right的后继
      if (leftPreNode) leftPreNode.next = curr; // left的前驱指向原right(新表头)
      
      // 移动指针,退出循环(后续节点无需处理)
      prev = curr;
      curr = temp;
    } else {
      // 情况4:当前节点在反转区间内,执行常规反转操作
      const temp: ListNode | null = curr.next; // 暂存下一个节点
      curr.next = prev; // 反转指针:当前节点指向前驱
      // 移动指针,继续下一个节点的反转
      prev = curr;
      curr = temp;
    }
    i++; // 计数递增
  }

  // 3. 返回虚拟头节点的next(真实表头)
  return dummy.next;
};

五、关键细节与易错点提醒

这道题很容易在细节上出错,分享几个高频易错点,帮大家避坑:

易错点1:计数从1开始

题目中 left 和 right 是「从1开始计数」的(比如链表 [1,2,3],left=1 就是第一个节点),所以我们的计数变量 i 要从1开始,而不是0,否则会定位错误。

易错点2:暂存后继节点

反转节点时,一定要先用 temp 暂存 curr.next,再修改 curr.next 的指向。如果直接修改 curr.next = prev,会丢失 curr 的下一个节点,导致链表断裂,无法继续遍历。

易错点3:边界节点的非空判断

代码中判断 if (reverseStart) 和 if (leftPreNode),是为了避免空指针异常。比如当 left=1 时,leftPreNode 其实是 dummy(非空);但如果链表为空,或者 left 超出链表长度,这些节点可能为 null,必须判断后再操作。

易错点4:反转后的连接顺序

反转完成后,必须先让 reverseStart.next = temp(连接反转区间的尾部和原链表的后续部分),再让 leftPreNode.next = curr(连接原链表的前部和反转区间的头部)。顺序颠倒不会报错,但会导致链表连接错误。

六、总结

LeetCode 92 题的核心是「局部反转 + 边界连接」,解题的关键在于:

  1. 用虚拟头节点简化表头反转的特殊处理;

  2. 精准定位 left 的前驱、反转区间的首尾节点;

  3. 反转过程中注意指针的移动顺序和暂存操作;

  4. 反转后正确连接边界,避免链表断裂。

这道题的迭代法时间复杂度是 O(n)(只需遍历链表一次),空间复杂度是 O(1)(仅使用几个指针变量),是最优解法。建议大家多手动模拟几遍指针移动过程,熟悉链表操作的细节,后续遇到类似的局部反转、指定节点操作等题目,就能举一反三了。

相关推荐
春日见1 小时前
如何查看我一共commit了多少个,是哪几个,如何回退到某一个版本
vscode·算法·docker·容器·自动驾驶
uesowys2 小时前
华为OD算法开发指导-二级索引-Read and Write Path Different Version
java·算法·华为od
TracyCoder1232 小时前
LeetCode Hot100(55/100)——347. 前 K 个高频元素
数据结构·算法·leetcode
码农三叔2 小时前
(11-4-03)完整人形机器人的设计与实现案例:盲踩障碍物
人工智能·算法·机器人·人机交互·人形机器人
Wect2 小时前
LeetCode 92. 反转链表II :题解与思路解析
前端·算法·typescript
Wect2 小时前
LeetCode 138. 随机链表的复制:两种最优解法详解
前端·算法·typescript
近津薪荼2 小时前
优选算法——前缀和(4):除了自身以外数组的乘积
算法
像颗糖2 小时前
OpenSpec 和 Spec-Kit 踩了 27 个坑之后,于是我写了个 🔥SuperSpec🔥 一次性填平
前端·后端
李派森2 小时前
AI大模型之丙午马年运势模型的构建与求解
笔记·算法