92. 反转链表 II

92. 反转链表 II

✨核心逻辑

本题采用 虚拟头节点 + 头插法(局部反转) 的策略:

  1. 虚拟头节点(Dummy Node):因为 left 有可能等于 1(即从链表头开始反转),为了方便统一处理,我们创建一个虚拟头节点指向原 head
  2. 定位前驱节点:通过循环,先让指针走到 left 位置的前一个节点(前驱节点)停下。
  3. 局部头插法反转:确认反转区间起点节点后,利用“头插法”,将 left 后面的节点依次移动到前驱节点(即区间头部)的后面。
  4. 完成拼接:通过循环重复 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前面,以此类推,循环终止时则完成