LeetCode 206/92/25 链表翻转问题-“盒子-标签-纸条模型”

目录

一、核心理论:盒子、标签、纸条

[二、题目 1:基础反转链表(LeetCode 206)](#二、题目 1:基础反转链表(LeetCode 206))

题目要求

解题思想

过程拆解

代码

[三、题目 2:局部反转链表 II(LeetCode 92)](#三、题目 2:局部反转链表 II(LeetCode 92))

题目要求

解题思想

过程拆解

代码(带理论注释)

[四、题目 3:K 个一组反转链表(LeetCode 25)](#四、题目 3:K 个一组反转链表(LeetCode 25))

题目要求

解题思想

过程拆解

代码

五、总结


链表反转是算法面试的高频考点,但很多人会被 "指针指向" 绕晕 ------ 其实只要用 "盒子、标签、纸条" 的比喻统一理论,不管是基础反转、局部反转还是 K 个一组反转,本质都是同一套逻辑。

一、核心理论:盒子、标签、纸条

链表的所有操作,都可以拆解为这三个角色的互动,记住它们的定位,反转问题就通了:

角色 本质 操作规则
盒子(节点) 链表的 "实体" 数量、内容全程不变(比如1、2、3这几个盒子),是所有操作的 "素材";
标签(指针) 操作的 "工具" 只负责 "贴在" 不同盒子上,移动标签≠修改盒子,只是换了操作目标;
纸条(next) 链表的 "连接关系" 链表的顺序由纸条指向决定,反转的本质就是修改纸条的方向,不是修改盒子本身;

盒子不变,标签挪,纸条改了链就变。

或者我们也可以把链表想象成放在地上的一排快递盒子

  1. 盒子 (Object/Node)

    • 这就是 new ListNode() 创建出来的东西。

    • 它一旦放在地上,位置通常是不动的(我们在脑海里不需要搬动盒子的物理位置,只需要改变它们的关系)。

  2. 标签 (Reference/Variable)

    • 比如 head, curr, prev, next

    • 这是一张张贴纸

    • curr = head 的意思不是复制盒子,而是把写着 curr 的标签,贴到了和 head 标签所在的同一个盒子上

    • 重要原则:移动标签,不会影响盒子之间的连接关系。

  3. 绳子 (Next Pointer)

    • 这就是 node.next

    • 每个盒子手里攥着一根绳子,拴着下一个盒子。

    • 链表翻转的本质:就是把这根绳子剪断,拴到另一个盒子上(通常是后面那个盒子拴回前面那个)。

二、题目 1:基础反转链表(LeetCode 206)

题目要求

给你单链表的头节点head,反转整个链表,返回反转后的头节点。示例:输入1→2→3→4→5,输出5→4→3→2→1

解题思想

用两个标签(prevcur)遍历所有盒子,逐个修改每个盒子的纸条方向(从 "指向下一个盒子" 改成 "指向前一个盒子")。

过程拆解

(示例:1→2→3→4→5)

  1. 初始状态

    • prev标签:贴在null(无盒子);
    • cur标签:贴在盒子 1;
    • 所有盒子的纸条:1→2→3→4→5
  2. 循环改纸条(共 5 轮):每轮做 3 件事:

    • 暂存cur盒子的纸条(避免丢失下一个盒子);
    • cur盒子的纸条改成 "指向prev当前贴的盒子";
    • 移动prevcur标签到下一个目标。

    以第 1 轮(处理盒子 1)为例:

    • 暂存盒子 1 的纸条(指向盒子 2);
    • 盒子 1 的纸条→指向prevnull);
    • prev标签贴到盒子 1,cur标签贴到盒子 2。
  3. 循环结束

    • cur标签贴到null(遍历完所有盒子);
    • prev标签贴到盒子 5(反转后的新头)。

代码

java 复制代码
public ListNode reverseList(ListNode head) {
    // prev标签:初始贴null
    ListNode prev = null;
    // cur标签:初始贴头盒子
    ListNode cur = head;
    
    while (cur != null) {
        // 暂存cur盒子的纸条(避免丢盒子)
        ListNode nextBox = cur.next;
        // 改cur盒子的纸条:指向prev当前贴的盒子
        cur.next = prev;
        // prev标签挪到cur当前的盒子
        prev = cur;
        // cur标签挪到暂存的下一个盒子
        cur = nextBox;
    }
    // prev最终贴在新头盒子上
    return prev;
}

三、题目 2:局部反转链表 II(LeetCode 92)

题目要求

给你链表头head和两个整数leftright,反转leftright位置的盒子,返回原链表。示例:输入1→2→3→4→5, left=2, right=4,输出1→4→3→2→5

解题思想

分 3 步:

  1. p0标签定位 "反转区间的前一个盒子"(锚点);
  2. 在区间内改盒子的纸条方向;
  3. 衔接 "区间前的盒子" 和 "反转后的区间"。

过程拆解

(示例:left=2,right=4)

  1. 定位锚点标签 p0

    • 初始p0标签贴在哑节点盒子(val=0,纸条指向盒子 1);
    • 循环left-1次(2-1=1 次),p0标签挪到盒子 1(反转区间的前一个盒子)。
  2. 反转区间内的纸条(盒子 2、3、4)

    • prev标签贴nullcur标签贴盒子 2(p0的纸条指向);
    • 循环right-left+1次(4-2+1=3 次),逐个改盒子 2、3、4 的纸条:
      • 盒子 2 的纸条→指向null
      • 盒子 3 的纸条→指向盒子 2;
      • 盒子 4 的纸条→指向盒子 3。
  3. 衔接链表

    • 盒子 2 的纸条→指向盒子 5(原区间后的第一个盒子);
    • 盒子 1 的纸条→指向盒子 4(反转后的区间头)。

代码(带理论注释)

java 复制代码
public ListNode reverseBetween(ListNode head, int left, int right) {
    // 哑节点盒子:避免头节点的特殊处理
    ListNode dummy = new ListNode(0, head);
    // p0标签:初始贴哑节点盒子
    ListNode p0 = dummy;
    
    // 定位p0到反转区间的前一个盒子
    int count = left;
    while (count > 1) {
        p0 = p0.next; // p0标签挪到下一个盒子
        count--;
    }
    
    // prev标签:初始贴null;cur标签:贴反转区间的第一个盒子
    ListNode prev = null;
    ListNode cur = p0.next;
    
    // 反转区间内的盒子纸条
    for (int i = 0; i < right - left + 1; i++) {
        ListNode nextBox = cur.next; // 暂存cur的纸条
        cur.next = prev; // 改cur的纸条指向prev
        prev = cur; // prev标签挪到cur
        cur = nextBox; // cur标签挪到暂存盒子
    }
    
    // 衔接:反转区间的新尾(原头)连后面的盒子
    p0.next.next = cur;
    // 衔接:p0连反转区间的新头
    p0.next = prev;
    
    return dummy.next;
}

四、题目 3:K 个一组反转链表(LeetCode 25)

题目要求

k个盒子一组反转,不足k个则保持原顺序,返回修改后的链表。示例:输入1→2→3→4→5, k=2,输出2→1→4→3→5

解题思想

分 3 步循环执行:

  1. 分组:用tail标签找到当前组的尾盒子(不足k个则终止);
  2. 反转组内:改组内盒子的纸条方向;
  3. 衔接:当前组的前锚点连反转后的组头,组尾连下一组头。

过程拆解

(示例:k=2)

  1. 初始状态

    • cur标签贴在哑节点盒子(纸条指向盒子 1)。
  2. 第 1 组(盒子 1、2)

    • 找 tail:cur标签挪 2 次,找到盒子 2(组尾);
    • 反转组内:盒子 1、2 的纸条→改成2→1
    • 衔接:哑节点的纸条→指向盒子 2,盒子 1 的纸条→指向盒子 3;
    • cur标签挪到盒子 1(下一组的前锚点)。
  3. 第 2 组(盒子 3、4)

    • 找 tail:cur标签挪 2 次,找到盒子 4(组尾);
    • 反转组内:盒子 3、4 的纸条→改成4→3
    • 衔接:盒子 1 的纸条→指向盒子 4,盒子 3 的纸条→指向盒子 5;
    • cur标签挪到盒子 3。
  4. 剩余盒子不足 k 个(盒子 5)

    • 终止循环,返回结果。

代码(带理论注释)

java 复制代码
public ListNode reverseKGroup(ListNode head, int k) {
    // 哑节点盒子:统一组头的处理
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    // cur标签:初始贴哑节点(作为组的前锚点)
    ListNode cur = dummy;
    
    while (cur.next != null) {
        // 找当前组的尾盒子tail
        ListNode tail = cur;
        for (int i = 0; i < k; i++) {
            tail = tail.next;
            if (tail == null) { // 不足k个,直接返回
                return dummy.next;
            }
        }
        
        // 暂存下一组的头盒子
        ListNode nextGroupHead = tail.next;
        // 反转当前组的盒子纸条,返回{新头, 新尾}
        ListNode[] reversed = reverse(cur.next, tail);
        ListNode newHead = reversed[0];
        ListNode newTail = reversed[1];
        
        // 衔接:前锚点连组新头,组新尾连下一组头
        cur.next = newHead;
        newTail.next = nextGroupHead;
        
        // cur标签挪到组新尾(作为下一组的前锚点)
        cur = newTail;
    }
    
    return dummy.next;
}

// 反转"head到tail"的盒子纸条,返回{新头, 新尾}
private ListNode[] reverse(ListNode head, ListNode tail) {
    // prev标签:初始贴tail的下一个盒子(组外锚点)
    ListNode prev = tail.next;
    // cur标签:初始贴组头盒子
    ListNode curr = head;
    
    // 当prev追上tail,说明组内纸条改完了
    while (prev != tail) {
        ListNode nextBox = curr.next; // 暂存cur的纸条
        curr.next = prev; // 改cur的纸条指向prev
        prev = curr; // prev标签挪到cur
        curr = nextBox; // cur标签挪到暂存盒子
    }
    // 反转后,原尾是新头,原头是新尾
    return new ListNode[]{tail, head};
}

五、总结

不管是基础、局部还是 K 组反转,核心逻辑完全一致

  • 盒子(节点)始终是原实体,从不新增 / 删除;
  • 用标签(指针)定位目标盒子,移动标签只是换操作对象;
  • 反转的本质是修改盒子里的纸条(next)方向,链表的顺序由纸条决定。

相关推荐
Benmao⁢2 小时前
C语言期末复习笔记
c语言·开发语言·笔记·leetcode·面试·蓝桥杯
菜鸟plus+2 小时前
N+1查询
java·服务器·数据库
唯道行2 小时前
计算机图形学·23 Weiler-Athenton多边形裁剪算法
算法·计算机视觉·几何学·计算机图形学·opengl
我要添砖java2 小时前
《JAVAEE》网络编程-什么是网络?
java·网络·java-ee
CoderYanger2 小时前
动态规划算法-01背包问题:50.分割等和子集
java·算法·leetcode·动态规划·1024程序员节
花月C2 小时前
个性化推荐:基于用户的协同过滤算法
开发语言·后端·算法·近邻算法
lxh01132 小时前
最长递增子序列
前端·数据结构·算法
Youyzq3 小时前
前端项目发布到cdn上css被编译失效问题rgba失效和rgb失效
前端·css·算法·cdn
风筝在晴天搁浅3 小时前
代码随想录 516.最长回文子序列
算法