出现 537 次的唯一 Hard:难在一根指针上

简介: 这道高频Hard题“K个一组翻转链表”看似思路极简(每k个分组内反转再拼接),实则暗藏三重陷阱:`prev`初值错致断链、游标更新错致重叠翻转、漏判不足k组致语义错误。真正难点不在设计,而在指针操作中精准掌控6个节点的归属与连接——改一根,必须记得其余五根在哪。

高频榜前十名里只有一道 Hard:K 个一组翻转链表,出现 537 次,排第 5。

但这题有个很迷惑人的特点——它的思路一句话就能说完。

把链表每 k 个切成一组,组内反转,再把各组接起来。讲完。你在面试里花 15 秒就能把这个方案讲给面试官听,然后开始写,然后大概率写崩。

我拿三种"看起来很合理"的写法做了穷举搜索,想找出每种写法最早在哪个输入上出错。结果挺有意思:其中一种错误写法,节点一个没丢、一个没多、长度完全正确,只有逐位比对序列才能发现它是错的。

这题考的根本不是你想不想得到方案,是你敢不敢在动了第一根指针之后,还记得其余五根在哪。


一、先把正解写出来

public ListNode reverseKGroup(ListNode head, int k) {
   
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode prevGroup = dummy;

    while (prevGroup.next != null) {
   
        ListNode kth = prevGroup;
        for (int i = 0; i < k && kth != null; i++) kth = kth.next;
        if (kth == null) break;                       // 不足 k 个,保持原序

        ListNode groupNext = kth.next;                // 本组的后继,反转的终止哨兵
        ListNode prev = groupNext, cur = prevGroup.next;   // ★ prev 初值是 groupNext,不是 null
        while (cur != groupNext) {
   
            ListNode nxt = cur.next;
            cur.next = prev;
            prev = cur;
            cur = nxt;
        }
        ListNode oldFirst = prevGroup.next;           // 原组头,反转后变成组尾
        prevGroup.next = kth;                         // 前驱接上新组头
        prevGroup = oldFirst;                         // ★ 游标移到组尾,不是 kth
    }
    return dummy.next;
}

二十来行,里面标了两个 ★。这两个星号加上 break 那一行,就是这题的全部难点。


二、逐帧看一遍:一组要经过六次指针交接

用 [1,2,3,4,5]、k=2 走一遍,把每一帧的 prev、cur、nxt 和链表当时的真实形态都记下来:

在这里插入图片描述

看第 3 帧有个细节值得单独说:节点 2 从 dummy 出发已经走不到了,它只被 cur 指着。

这就是为什么代码里必须先 ListNode nxt = cur.next; 再改 cur.next = prev;。一旦先改了 cur.next,后半段链表就只能靠你事先存下的那个引用找回来了——顺序写反,链表当场断掉,而且断得很安静。

再看最后两帧。第二组接回后链表是 2→1→4→3→5,游标停在节点 3(原组头,现组尾)。下一轮从它出发走 2 步:3→5→null,撞到 null,于是 break,剩下的 5 保持原序。

"不足 k 个保持原序"这条规则,就是靠这个 break 实现的。 它看着最不起眼,但漏掉它的代码在 LeetCode 上会直接判错——见下一节 bug3。


三、三种典型错误,和它们各自的最小反例

我把三个最容易写错的地方各做成一个 bug 版本,然后让程序在 len ≤ 8 的全排列空间里穷举搜索,找每种错误最早暴露的输入。

在这里插入图片描述

bug 错误写法 最小反例 错误输出 正确输出
bug1 组内反转的 prev 初值写成 null [1,2,3] k=2 [2, 1] [2, 1, 3]
bug2 游标更新写成 prevGroup = kth [1,2,3] k=2 [2, 3, 1] [2, 1, 3]
bug3 不足 k 个的尾巴也硬反转 [1,2,3,4,5] k=3 [3, 2, 1, 5, 4] [3, 2, 1, 4, 5]

三个 bug 的失败形态完全不同:

bug1 是断链。prev 初值是 null,第一组反转完,原组头节点 1 指向了 null,节点 3 从此失联。输出只剩两个节点。这个错误最"良心",因为它会体现在长度上。

bug2 最阴。游标本该移到"组尾"(也就是原组头 oldFirst),写成 kth 就移到了"组头"。下一轮从这个位置再走 k 步,取到的区间和上一组重叠了。结果 [1,2,3] 变成 [2,3,1]——三个节点全在,一个不多一个不少,只是顺序不对。

bug3 是语义错。末组只有两个节点,硬反转成 [5,4]。节点也全在。

于是有个很不舒服的结论:bug2 和 bug3 都能通过"数一下长度对不对"和"元素齐不齐"这两种自查。 一个节点没丢、没重复,值的多重集完全守恒。你在面试里手写完,快速扫一眼"嗯,1 2 3 4 5 都在",就放过去了。

唯一能抓住它们的,是逐位比对序列。


四、所以链表题应该怎么自查

我自己写验证程序时的做法,是拿一个笨到不可能错的参考实现做基准:先把值全部取进数组,按题意在数组里重排(够 k 个就反转这一段,不够就原样留着),再照着新数组重建一条链表。

这个参考实现的时间空间都很差,但它的正确性一眼可见。然后给它配三重检查,按代价从低到高排:

1) 节点数守恒   —— 从 head 走,步数超过原长即判定成环;步数不足即判定丢节点
2) 值多重集一致 —— 排序后比对,抓重复节点和丢节点
3) 逐位序列比对 —— 与参考实现的结果逐个比

第 1 重检查必须写成"步数上限"而不是"走到 null 为止"。因为断链重连的另一端失败形态是成环,while (cur != null) 会永远走不完。我在写 bug2 的搜索时专门加了步数上限保护,就是防这个。

穷举 + 随机的结果:

穷举组合=300(长度 0..9 × k 1..10 × 三种数据形态,迭代/递归各跑一遍)不一致=0
随机用例=50000(长度 0..59,k 1..12)新增不一致=0

面试时你没法跑测试,但这三重检查对应的自查动作是可以手做的,而且很便宜:

  • 拿 [1,2,3,4,5] k=2 心算一遍,答案是 2→1→4→3→5。这是 LeetCode 官方第一个用例,也是能同时暴露三个 bug 的最小输入。
  • 拿 k=1 自检。每组一个节点,反转是恒等操作,答案必须等于原链表。如果你的代码在 k=1 时结果变了,说明组的边界算错了。
  • 拿 k 大于长度自检。一个节点都不该动。

这三个用例覆盖掉了这题所有的失败分支,成本是三十秒。


五、三种写法,和一个我亲自中的 off-by-one

在这里插入图片描述

迭代版是面试首选,O(1) 额外空间。

递归版更短,把"接回下一组"这件事交给返回值:

public ListNode reverseKGroupRec(ListNode head, int k) {
   
    ListNode kth = head;
    for (int i = 1; i < k && kth != null; i++) kth = kth.next;   // 注意是 i = 1
    if (kth == null) return head;
    ListNode groupNext = kth.next, prev = groupNext, cur = head;
    while (cur != groupNext) {
    ListNode nx = cur.next; cur.next = prev; prev = cur; cur = nx; }
    head.next = reverseKGroupRec(groupNext, k);   // 原组头(现组尾)接下一组的结果
    return kth;                                   // 新组头
}

这里有个我自己真的中过的坑。 迭代版里找第 k 个节点是 kth = prevGroup 然后走 k 步,因为 prevGroup 在组外面;递归版里起点是 head,它已经是组内第 1 个了,只能走 k−1 步。

我写递归版时直接照抄了 for (i = 0; i < k; i++),结果 kth 落到了下一组的第一个节点上。穷举测试跑第一遍就报了一片:len=2, k=1 期望 [1,2] 实际 [2,1]——k=1 时它把整条链表反转了。

两版混着抄,是最容易中的毒。 面试官让你"再给一个递归写法",考的往往不是你会不会递归,是你有没有注意到起点变了、步数也得跟着变。

递归版另一个追问点是空间:深度是 n/k。n=10⁵、k=2 时递归 5 万层,Java 默认栈是可能 StackOverflow 的。面试官问"能不能不用递归",就是在等这句。

值回填法——把值取进数组、重排、写回原节点——在 LeetCode 上能 AC。但它经不起追问:如果节点挂着大对象、或者题目要求"真正移动节点",这个写法直接出局。它能过,只是因为这道题恰好只关心值的顺序。用之前先想清楚这一点,别默认它是最优解。


六、这题的追问清单

追问 想确认什么 一句话答案
prev 为什么初始化成 groupNext 是否理解组间衔接 原组头反转后要指向下一组开头,指 null 就断链
游标为什么移到 oldFirst 是否理解组尾变了 反转后原组头成了组尾,下一组要接在它后面
不足 k 个怎么办 有没有读清题 保持原序,靠"走 k 步撞 null 就 break"实现
递归版空间多少 会不会算复杂度 O(n/k) 栈深度,n 大时有爆栈风险
k=1 时结果应该是什么 有没有自检习惯 必须等于原链表,不等就是组边界算错
递归版怎么找第 k 个节点 会不会照抄出错 起点是 head,只能走 k−1 步
值回填能过吗 知不知道解法边界 能过 LeetCode,但没真正移动节点,经不起追问
如果要求每 2k 组只翻前 k 个 能不能泛化 加一个组序号奇偶判断即可,骨架不变

金句:链表题的难,从来不在"想到怎么做",而在"改了指针之后,你还记得有几个节点已经不属于原来的位置了"。


下一篇想写排名第 3 的 反转链表,出现 758 次。它是这十道里唯一标着 Easy 的,七行代码,看起来最没得可讲。但它是前十名里唯一一道递归版和迭代版复杂度不同的题——而且面试官真正想问的那句,是"递归返回的时候,栈上每一层的 head 分别指向谁"。

本文代码与配图由作者用华为云码道(CodeArts 代码智能体)辅助完成,三种写法与三个错误版本均经穷举与随机对撞验证。

相关文章
|
4月前
|
人工智能 JSON 开发工具
当 Agent 学会了"动手"——阿里云百炼 CLI 深度体验:一个命令行如何重新定义 AI 创造力
百炼CLI是阿里云推出的AI命令行工具,两年打磨让AI从“思考”迈向“行动”。它集成文生图、视频、配音、文案等多模态能力,统一接口、Agent原生支持,让创意从想法秒变产品。终端一行命令,即刻生成专业级电商素材——不是又一个工具,而是AI时代的“通用创作协议”。
510 0
|
6月前
|
人工智能 自然语言处理 搜索推荐
我用 OpenClaw 玩转漫评 skill:成为漫剧影评助手达人不是梦
本文分享作者从“影评小白”到“圈内达人”的蜕变历程,详解如何用AI助手OpenClaw一站式解决信息搜集、数据整理、文案创作与视觉设计难题,将单篇影评耗时从8–12小时压缩至10–15分钟,效率提升48–72倍,并附实战案例、部署教程与高效技巧。
857 6
|
6月前
|
人工智能 JavaScript 安全
我在阿里云轻量服务器上养了一只"高情商龙虾",它比我更会说话
我用阿里云轻量服务器部署开源AI框架OpenClaw,打造专属高情商AI“龙虾”:仅靠三份配置文件(SOUL/USER/AGENTS.md)赋予其共情力、人格与行为逻辑。它已真实助我化解朋友情感危机、职场敬酒难题——不给话术,直击情绪底层需求。5分钟部署,9.9元/月起。
523 1
|
2天前
|
人工智能 自然语言处理 安全
阿里云百炼产品月报【2026年9月】
阿里云百炼本月重磅升级:Qwen3.8全模态实时模型上线,Token Plan取消周限额、新增Essential套餐及Agent Harness工具权益;Flow Agent预置模板即开即用,MCP广场上新46项服务,覆盖科研、金融、多媒体等场景;应用与Skill广场新增超30款模板及解决方案,控制台全面焕新,助力企业高效构建AI应用。
184 0
|
2天前
|
人工智能 缓存 算法
2026 后端工程师面试技术栈汇总(国内大厂/中厂 · 校招/初级向)
一句话:**地基没换,但地基上的"考核方式"换了,并且新盖了一层 AI 工程化。**
54 0
|
10月前
|
SQL 人工智能 分布式计算
【MaxCompute SQL AI 实操教程】0元体验使用大模型提效数据分析
【MaxCompute SQL AI 实操教程】0元体验使用大模型提效数据分析
969 4
|
人工智能 缓存 前端开发
通义灵码2.5+qwen3——节假日抢票不用愁,基于12306-MCP实现个人火车票智能查询小助手!
本项目作为通义灵码2.5的深度实践案例,充分展现了通义灵码2.5编程智能体调用MCP实现大模型智能化工具的强大优势。
806 10

热门文章

最新文章