快乐算法 —— 合并两个有序链表

97 阅读3分钟

happy

合并两个有序链表

先给答案:

/**
* Definition for singly-linked list.
* function ListNode(val) {
*  this.val = val;
*  this.next = null;
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/

const mergeTwoLists = function (l1, l2) {
  if (l1 === null) {
    return l2;
  }
  if (l2 === null) {
    return l1;
  }
  if (l1.val < l2.val) {
    l1.next = mergeTwoLists(l1.next, l2);
    return l1;
  } else {
    l2.next = mergeTwoLists(l1, l2.next);
    return l2;
  }
}

再看题:

将两个升序链表,合并为,一个新的升序链表,并返回。

新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

输入: 1->2->4, 1->3->4

输出: 1->1->2->3->4->4

关键点

  • 要知道 链表数据结构
  • 要考虑 边界这种情况

题目要求使用递归方法合并两个有序链表,基本思路是:

1、比较两个链表头节点的值。

2、将较小的节点作为合并后链表的头节点。

3、递归地将该节点的next指针指向剩余链表的合并结果。

4、当任一链表为空时终止递归。


复杂度

MN是两条链表l1l2的长度

M、N 是两条链表 l1、l2 的⻓度

时间复杂度:O(M+N)

空间复杂度:O(M+N)

复杂度分析:

时间

  • M 是链表 l1 的长度,N 是链表 l2的长度。
  • 每次递归调用处理一个节点,总共需要处理M+N个节点。
  • 每个节点的比较和连接操作都是常数时间 O(1)。
  • 因此总时间复杂度与两个链表的总长度成正比。

空间

  • 主要来自递归调用栈的消耗。
  • 最坏情况下(如两个链表完全交错),递归深度为M+N
  • 每次递归调用都会在调用栈中占用一个栈帧空间。
  • 因此空间复杂度也与链表总长度成正比。

迭代

var mergeTwoList = function (l1, l2) {
  // 1、创建虚拟头节点
  const prehead = new ListNode(-1);
  
  // 2、维护一个prev指针指向当前合并链表的末尾
  let prev = prehead;
  
  // 3、循环比较两个链表的节点
  while(l1 != null && l2 != null){
    if (l1.val <= l2.val) {
      prev.next = l1; // 将l1节点连接到结果链表
      l1 = l1.next; // l1指针后移
    } else {
      prev.next = l2; // 将l2节点连接到结果链表
      l2 = l2.next; // l2指针后移
    }
    prev = prev.next; // 结果链表指针后移
  }
  
  // 4、处理剩余节点
  prev.next = l1 === null ? l2 : l1;
  
  // 5、返回合并后的链表(跳过虚拟头节点)
  return prehead.next;
}

步骤:

1、虚拟头节点:

  • 创 一个 值 为 -1 的虚拟点作为合并链表的起始点。
  • 简化了边界条件处理,无需单独处理空链表情况。

2、prev指针:

  • 始终指向当前合并链表的最后一个节点。
  • 用于连接下一个最小值的节点。

3、主循环:

  • 比较l1和l2当前节点的值。
  • 将较小值的及节点连接到prev.next
  • 移动较小值节点所在链表的指针。
  • 移动prev指针到新连接的节点。

4、剩余节点处理:

  • 当任一链表遍历完后,直接将另一链表的剩余部分连接到结果链表。
  • 三元运算符判断哪个链表还有剩余节点。

5、返回结果:

  • 返回prehead.next,跳过虚拟头节点。
  • 得到的就是合并后的有序链表。

演示:

假设合并以下两个链表:

l1: 1 -> 3 -> 5
l2: 2 -> 4 -> 6

执行过程:

1、创建虚拟节点 - 1

2、第一次循环:1 < 2 -> 连接1,l1后移。

3、第二次循环:3 < 2 -> 连接2,l2后移。

4、第三次循环:3 < 4 -> 连接3,l1后移。

5、第四次循环:5 > 4 -> 连接4,l2后移。

6、第五次循环:5 < 6 -> 连接5,l1后移。

7、l1为null,循环结束。

8、连接l2剩余部分(6)。

9、最终结果:1 -> 2 -> 3 -> 4 -> 5 -> 6 。