面试知识库

21. 合并两个有序链表#

生活中的合并#

想象你正在整理两叠按日期排好序的收据。最自然的方式就是:拿起两叠收据,每次比较最上面的日期,选择日期较早的那张放入新的一叠中。这个简单的日常操作,恰恰就是我们今天要讨论的有序链表合并问题的真实写照。

问题描述#

题目目标#

LeetCode第21题”合并两个有序链表”要求:将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例 1#

输入: 1 → 2 → 4, 1 → 3 → 4 输出: 1 → 1 → 2 → 3 → 4 → 4 链表示意:

list1: 1 -> 2 -> 4 -> null
list2: 1 -> 3 -> 4 -> null
结果:  1 -> 1 -> 2 -> 3 -> 4 -> 4 -> null
text

说明: 每次取两个链表当前较小的节点,合并后的结果仍然保持升序。

示例 2#

输入: 空链表, 0 输出: 0 链表示意:

list1: null
list2: 0 -> null
结果:  0 -> null
text

说明: 其中一个链表为空时,合并结果就是另一个链表本身。

补充说明#

  • 链表题的输入本质上是节点之间的连接关系,重点通常是返回处理后的头节点或目标节点。

暴力解法:转换为数组排序#

最直观的想法可能是:把两个链表的值都放到一个数组里,排序后再创建新链表。这种方法虽然不够优雅,但对于理解问题很有帮助。

暴力解法实现#

优化解法:双指针遍历#

既然输入的链表已经排好序,我们完全可以模拟整理收据的过程:同时遍历两个链表,每次选择较小的节点连接到结果链表中。这就像拉链一样,将两个有序序列合并成一个。

代码实现与详解#

图解过程#

复杂度比较#

暴力解法:

  • 时间复杂度:O(nlogn),主要来自排序过程
  • 空间复杂度:O(n),需要额外数组存储所有节点
  • 缺点:没有利用链表已排序的特性

双指针解法:

  • 时间复杂度:O(n),只需要遍历一次
  • 空间复杂度:O(1),只需要几个指针
  • 优点:充分利用了输入链表已排序的特性

技巧与思考#

  1. 哨兵节点的妙用

    • 使用哨兵节点可以统一边界情况处理
    • 避免了对头节点的特殊处理
  2. 就地合并的思想

    • 不需要创建新节点
    • 通过改变指针指向来实现合并
  3. 处理剩余节点

    • 直接连接剩余链表
    • 避免了继续遍历剩余节点

实际应用延伸#

合并有序链表的思想在实际开发中很常见:

  1. 数据库中的有序结果集合并
  2. 文件系统中有序文件的合并
  3. 日志系统中按时间戳排序的日志合并

小结#

合并有序链表看似简单,实则蕴含着重要的算法思想:

  1. 如何高效处理有序数据
  2. 指针操作的技巧
  3. 如何简化边界条件处理

记住:当遇到类似的合并问题时,先考虑数据是否有序,如果有序,往往可以设计出更优雅高效的解法。