一、题目与考点拆解题目要求将两个升序链表合并为一个新的升序链表并返回新链表由给定的两个链表的所有节点拼接组成。输入两条各自升序排列的单链表输出合并后的升序单链表要求直接复用原链表节点不需要新建节点存值核心考点这道题真正考察的不是逻辑复杂度而是链表操作的基本功指针的移动与拼接理解「修改 next 指针就是修改链表结构」哑节点哨兵节点的使用统一头节点与中间节点的处理逻辑边界处理空链表、一条先走完的剩余拼接最优解为双指针迭代法时间复杂度 O (nm)两条链表各遍历一次空间复杂度 O (1)仅用常数个指针变量。二、核心思路哑节点 双指针迭代两条链表本身都是升序的我们只需要用两个指针分别指向两条链表的当前节点每次选值更小的节点拼接到结果链表尾部即可。核心技巧是哑节点Dummy Node先创建一个无业务值的占位头节点所有节点都统一拼接到它的后面彻底消除「第一个节点到底属于哪条链表」的特殊判断代码逻辑极度简洁。整体流程创建哑节点作为结果链表的占位头用一个尾部指针 cur 永远指向已拼接部分的最后一个节点同时遍历两条链表谁的值更小就把谁的节点接到 cur 后面对应链表指针后移一步每次拼接完成后cur 同步后移一步永远保持在结果链表尾部当其中一条链表遍历完毕时直接把另一条链表的剩余部分整条接到尾部最终返回哑节点的 next也就是真正的结果链表头节点三、最终定稿 AC 代码class Solution { public ListNode mergeTwoLists(ListNode list1, ListNode list2) { // 哑节点占位用的空头节点统一拼接逻辑 ListNode head new ListNode(); // 尾部指针永远指向当前已拼接链表的最后一个节点 ListNode cur head; // 两条链表都不为空时两两比较拼接 while (list1 ! null list2 ! null) { if (list1.val list2.val) { cur.next list1; list1 list1.next; } else { cur.next list2; list2 list2.next; } // 尾部指针同步后移 cur cur.next; } // 收尾谁还有剩余直接整条接在末尾 cur.next list1 ! null ? list1 : list2; // 哑节点本身无意义返回真正的头节点 return head.next; } }四、逐行深度拆解1. 初始化哑节点 尾部指针ListNode head new ListNode(); ListNode cur head;head是哑节点本身不存储有效数据只起占位作用。 没有哑节点的话需要单独判断第一个节点来自 list1 还是 list2单独给结果链表的头赋值逻辑繁琐且容易出空指针有了哑节点所有节点都用cur.next xxx的统一逻辑拼接。cur是尾部指针永远指向已经拼好的链表的最后一个节点负责承接下一个新节点。初始时和哑节点重合代表还没有拼接任何有效节点。2. 循环拼接主体while (list1 ! null list2 ! null) { if (list1.val list2.val) { cur.next list1; list1 list1.next; } else { cur.next list2; list2 list2.next; } cur cur.next; }循环条件必须是只有两条链表都还有节点时才继续两两比较只要有一条走完了就退出循环。 ❌ 如果写成||会出现某条链表已经为空还去取 val 的情况直接空指针异常。拼接顺序先把节点接到 cur 后面再把对应链表的指针往后挪一步。最容易漏的一行cur cur.next接上新节点后尾部指针必须同步后移否则下一次拼接会覆盖掉上一个节点最终结果只剩最后一个节点。这是链表题最高频的手写错误。3. 收尾三目运算符拼接剩余链表cur.next list1 ! null ? list1 : list2;这是新手最容易疑惑的一行拆开讲透循环退出的必然结果因为循环条件是两条都不为空才继续所以退出时一定是「一条已经空了另一条还有剩余」不可能两条同时剩也不会两条同时空除非输入本身全空。为什么能直接整条接剩余的链表本身就是升序的且所有节点的值都大于等于已经拼好的节点不需要再遍历比较直接整条接上即可。这是链表结构的优势 —— 只改一个 next 指针就能接上一整段不需要逐个拷贝。语法等价这是 Java 三元运算符和下面的 if-else 完全等价if (list1 ! null) { cur.next list1; } else { cur.next list2; }作用就是判断谁还有剩余就把谁剩下的整条链表接在结果尾部。4. 返回结果return head.next;head是我们自己创建的哑节点没有业务意义真正的结果链表从它的下一个节点开始。 ❌ 新手高频坑直接返回head会导致结果多一个无意义的默认值头节点。五、新手必踩坑清单坑 1漏掉cur cur.next现象所有节点都被覆盖最终结果只剩最后一个节点原因尾部指针没有同步后移每次拼接都在同一个位置覆盖坑 2直接返回 head 而非 head.next现象结果链表开头多了一个值为 0 的无效节点原因忘记哑节点只是占位用的真正的头在它的 next坑 3循环条件写成 ||现象运行时空指针异常原因一条链表走完后还继续访问它的 val必然报错坑 4收尾部分还要写循环逐个拼接现象逻辑冗余代码变长原因没利用好链表本身的有序性和指针特性剩余部分整条拼接即可无需遍历坑 5不用哑节点单独处理头节点现象逻辑分支多边界判断繁琐极易出错原因没掌握哑节点技巧链表拼接题优先上哑节点是通用最优解六、拓展递归版实现这道题也有非常简洁的递归写法核心思想完全一致选出当前更小的节点它的 next 等于「两条剩余链表的合并结果」。class Solution { public ListNode mergeTwoLists(ListNode list1, ListNode list2) { // 递归终止一条为空直接返回另一条 if (list1 null) return list2; if (list2 null) return list1; if (list1.val list2.val) { list1.next mergeTwoLists(list1.next, list2); return list1; } else { list2.next mergeTwoLists(list1, list2.next); return list2; } } }递归版代码更短但递归栈会带来 O (nm) 的空间开销面试手写优先推荐迭代版递归版可作为思路补充。七、面试相关口述思路直接背这道题我用迭代法加哑节点来做。首先创建一个哑节点作为结果链表的占位头用一个指针维护当前尾部然后同时遍历两条有序链表每次选择值更小的节点拼接到尾部对应链表指针后移。当其中一条链表遍历完时直接把另一条的剩余部分接在尾部最终返回哑节点的下一个节点。时间复杂度是 O (nm)两条链表各遍历一次空间复杂度 O (1)只用到常数个指针变量。高频追问为什么要用哑节点统一头节点和中间节点的处理逻辑不需要单独判断第一个节点属于哪条链表简化边界处理减少空指针风险。时间和空间复杂度迭代版时间 O (nm)空间 O (1)递归版时间相同空间 O (nm) 递归栈开销。这道题是归并排序的哪一步对应归并排序的「合并两个有序区间」步骤只是数组换成了链表逻辑完全一致。合并 k 个有序链表怎么做可以用分治思路两两合并也可以用优先队列每次取出值最小的节点。八、复习速记口诀哑节点做排头尾针跟着节点走 谁小接谁指针移剩的整条接后头。总结合并两个有序链表是链表题型的基础模板题核心套路非常固定。这道题最大的价值不是学会合并本身而是掌握哑节点这个链表题的通用神器 —— 后续的链表翻转、删除节点、链表分区等题目都可以用哑节点来简化边界处理。复习优先级先记核心框架哑节点 尾指针 双链表遍历 剩余拼接再避两个高频坑别忘移尾针、别返回哑节点本身最后形成条件反射只要是链表拼接 / 头节点不确定的题先写哑节点