LeetCode 21. 合并两个有序链表:两种经典解法详解

LeetCode 上的经典入门题------21. 合并两个有序链表,这道题是链表操作的基础题型,不仅考察对链表结构的理解,还能帮我们熟练掌握递归和迭代两种核心编程思路,适合新手入门练习,也适合老司机回顾基础。

先来看题目要求,帮大家梳理清楚核心考点:

一、题目解析

题目描述:将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的,不允许创建新的节点(仅拼接原有节点)。

核心要点拆解:

  • 输入:两个升序的单链表(可能为空链表,即 list1 或 list2 为 null);

  • 输出:一个新的升序单链表,节点全部来自两个输入链表,保持升序顺序;

  • 关键约束:不能创建新节点,只能通过调整原有节点的 next 指针实现拼接;

  • 难度:简单(入门级),核心考察「链表遍历」「递归/迭代逻辑」。

先给出题目中定义的链表节点类(TypeScript 版本),后续两种解法均基于该类实现:

typescript 复制代码
class ListNode {
  val: number
  next: ListNode | null
  constructor(val?: number, next?: ListNode | null) {
    this.val = (val === undefined ? 0 : val)
    this.next = (next === undefined ? null : next)
  }
}

二、解法一:递归解法(mergeTwoLists_1)

2.1 解题思路

递归的核心思想是「分解问题」------将"合并两个长链表"的大问题,分解为"合并两个更短链表"的小问题,直到触发终止条件(其中一个链表为空)。

具体逻辑:

  1. 终止条件:如果 list1 为空,直接返回 list2(剩下的节点都来自 list2);如果 list2 为空,直接返回 list1(剩下的节点都来自 list1);

  2. 递归逻辑:比较 list1 和 list2 的当前节点值,选择值更小的节点作为当前拼接节点;

  3. 递归调用:被选中节点的 next 指针,指向「剩余链表」的合并结果(即递归调用自身,传入被选中节点的下一个节点和另一个链表);

  4. 返回值:每次返回当前选中的节点,最终拼接成完整的升序链表。

举个简单例子帮助理解:

list1 = [1,3,5],list2 = [2,4,6] → 第一次比较 1 和 2,选 1,1 的 next 指向 mergeTwoLists_1([3,5], [2,4,6]);

第二次比较 3 和 2,选 2,2 的 next 指向 mergeTwoLists_1([3,5], [4,6]);

依次递归,直到其中一个链表为空,拼接剩余节点,最终得到 [1,2,3,4,5,6]。

2.2 完整代码

typescript 复制代码
function mergeTwoLists_1(list1: ListNode | null, list2: ListNode | null): ListNode | null {
  // 终止条件:其中一个链表为空,返回另一个
  if (list1 === null) {
    return list2;
  }
  if (list2 === null) {
    return list1;
  }
  // 递归逻辑:选择当前值更小的节点,拼接剩余链表
  if (list1.val < list2.val) {
    list1.next = mergeTwoLists_1(list1.next, list2);
    return list1;
  } else {
    list2.next = mergeTwoLists_1(list1, list2.next);
    return list2;
  }
};

2.3 解法分析

  • 时间复杂度:O(m + n),其中 m、n 分别是两个链表的长度。每个节点只会被访问一次,递归调用次数等于节点总数;

  • 空间复杂度:O(m + n),递归调用会占用栈空间,栈的深度等于两个链表的总长度(最坏情况,如两个链表完全有序且串联,递归深度为 m+n);

  • 优点:代码简洁、逻辑清晰,无需手动遍历链表,符合"分而治之"的思想,容易理解;

  • 缺点:栈空间占用较高,若链表过长,可能会出现栈溢出(但 LeetCode 测试用例不会出现这种极端情况,日常练习可放心使用)。

三、解法二:迭代解法(mergeTwoLists_2)

3.1 解题思路

迭代解法的核心思想是「手动遍历」------通过一个指针,依次比较两个链表的当前节点,将值更小的节点拼接在新链表后面,直到其中一个链表遍历完毕,再将剩余节点拼接在末尾。

这里有个小技巧:使用「虚拟头节点(dummy node)」。因为新链表的头节点不确定(可能是 list1 的第一个节点,也可能是 list2 的第一个节点),虚拟头节点可以简化头节点的处理,无需单独判断头节点的情况。

具体逻辑:

  1. 创建虚拟头节点 dummy(val 可任意,此处设为 0),再创建一个 curr 指针,指向 dummy(curr 用于遍历和拼接新链表);

  2. 循环遍历:当 list1 和 list2 都不为空时,比较两者当前节点值;

  3. 拼接节点:将值更小的节点赋值给 curr.next,然后将该链表的指针后移(list1 或 list2 后移),同时 curr 指针也后移;

  4. 处理剩余节点:循环结束后,必有一个链表为空,将剩余链表直接拼接在 curr.next 后面;

  5. 返回结果:返回 dummy.next(虚拟头节点的下一个节点,即新链表的真实头节点)。

3.2 完整代码

typescript 复制代码
function mergeTwoLists_2(list1: ListNode | null, list2: ListNode | null): ListNode | null {
  // 虚拟头节点,简化头节点处理
  const dummy = new ListNode(0);
  // curr 指针用于拼接新链表
  let curr = dummy;
  // 循环遍历两个链表,直到其中一个为空
  while (list1 && list2) {
    if (list1.val < list2.val) {
      curr.next = list1;
      list1 = list1.next; // list1 后移
    } else {
      curr.next = list2;
      list2 = list2.next; // list2 后移
    }
    curr = curr.next; // curr 后移,准备拼接下一个节点
  }
  // 拼接剩余节点(其中一个链表已为空)
  curr.next = list1 || list2;
  // 返回新链表的真实头节点
  return dummy.next;
};

3.3 解法分析

  • 时间复杂度:O(m + n),与递归解法一致,每个节点只被访问一次,循环次数等于节点总数;

  • 空间复杂度:O(1),仅使用了 dummy 虚拟节点和 curr 指针,没有额外占用空间(不考虑输入链表本身的空间);

  • 优点:空间效率高,不会出现栈溢出问题,适合处理长链表,逻辑直观,容易调试;

  • 缺点:代码比递归稍长,需要手动控制指针移动,容易出现指针操作错误(如忘记移动 curr 指针)。

四、两种解法对比 & 实战建议

对比维度 递归解法 迭代解法
时间复杂度 O(m + n) O(m + n)
空间复杂度 O(m + n)(栈空间) O(1)(常数空间)
代码简洁度 高(无需手动控制指针) 中等(需手动移动指针)
适用场景 链表较短,追求代码简洁 链表较长,追求空间效率
易错点 递归终止条件遗漏 指针移动遗漏(如 curr 未后移)

实战建议:

  • 面试时:优先写迭代解法!因为迭代解法空间复杂度更低,且不会有栈溢出风险,更能体现对指针操作的掌握(面试官更看重这一点);

  • 日常练习:两种解法都要掌握!递归解法能锻炼"分而治之"的思维,迭代解法能夯实链表操作基础,两者结合能更深入理解题目;

  • 调试技巧:遇到指针操作错误时,可手动模拟指针移动过程(如拿纸画链表节点和指针位置),快速定位问题。

五、总结

LeetCode 21 题看似简单,但涵盖了链表操作的核心考点------虚拟头节点、指针移动、递归/迭代逻辑,是入门链表的绝佳题目。

核心总结:

  1. 递归解法:利用终止条件分解问题,代码简洁,空间复杂度较高;

  2. 迭代解法:利用虚拟头节点+指针遍历,空间效率高,适合实战;

  3. 无论哪种解法,核心都是「依次比较两个链表的节点,保持升序拼接」,关键是处理好"空链表"和"指针移动"这两个细节。

相关推荐
70asunflower1 小时前
TypeScript / JavaScript / Node.js:现代工程化语言体系全景解析
javascript·typescript·node.js
2501_941982052 小时前
Python开发:外部群消息自动回复
java·前端·数据库
Epiphany.5562 小时前
蓝桥杯2024年第十五届决赛真题-套手镯
c++·算法·蓝桥杯
Σίσυφος19002 小时前
E = Kᵀ F K 的数学原理
算法
墨染青竹梦悠然2 小时前
基于Django+vue的单词学习平台
前端·vue.js·后端·python·django·毕业设计·毕设
小O的算法实验室2 小时前
2025年JIM SCI2区,基于Q学习多目标粒子群算法+节能型分布式流水车间调度,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
mCell2 小时前
从一个想法到可发布:我把博客接进 MCP 的完整实践
前端·node.js·mcp
YunchengLi2 小时前
【计算机图形学中的四元数】1/2 Quaternions for Computer Graphics
人工智能·算法·机器学习
Dragon Wu2 小时前
Zod 常用案例总结
前端·javascript·typescript