LeetCode 21 - 合并两个有序链表

题目信息

  • 题目编号: 21
  • 题目名称: 合并两个有序链表
  • 标签: 链表, 递归
  • 难度: 简单
  • 题目链接 : LeetCode 21

题目描述

将两个升序排列的链表 list1list2 合并为一个新的升序链表。新链表应该通过拼接前两个链表的节点来创建。

返回合并后的链表头节点。

示例

复制代码
示例 1:
输入: list1 = [1,2,4], list2 = [1,3,4]
输出: [1,1,2,3,4,4]

示例 2:
输入: list1 = [], list2 = []
输出: []

示例 3:
输入: list1 = [], list2 = [0]
输出: [0]

约束条件

  • 两个链表的长度范围为 [0, 50]
  • -100 <= Node.val <= 100
  • 两个链表均按非递减顺序排序

解题思路

初步思考

这道题是链表操作中的经典题目。合并两个有序链表,本质上就是"把两个已经排好序的序列重新合并成一个有序序列"。

最直观的思路是逐个比较两个链表的节点值,把较小的节点接上去。这就像有两副已经排好序的扑克牌,要把它们合并成一副更大的有序扑克牌------我们只需要每次比较两副牌的最上面一张,把较小的那张拿出来放到新牌堆里。

方法一:迭代法(推荐)

思路:

我们使用一个虚拟头节点(dummy node)来简化边界条件的处理,然后遍历两个链表,将较小的节点依次接到新链表后面。

关键点:

  1. 使用虚拟头节点 dummy,这样不需要单独处理头节点
  2. 使用指针 current 追踪新链表的当前位置
  3. 每次比较 list1list2 当前节点的值,将较小的节点接到 current 后面
  4. 当其中一个链表遍历完后,直接将另一个链表接上去

根据一个简单示例,通过图示展示思路:

list1 = [1,2,4], list2 = [1,3,4] 为例:

复制代码
初始状态:
  list1:  1 -> 2 -> 4 -> null
  list2:  1 -> 3 -> 4 -> null
  dummy -> null

步骤1: 比较 1 和 1,相等,取 list1 的 1
  list1:  1 -> 2 -> 4 -> null
  list2:  1 -> 3 -> 4 -> null
  dummy -> 1 -> null
  current 指向 1

步骤2: 比较 2 和 1,1 较小,取 list2 的 1
  list1:  1 -> 2 -> 4 -> null
  list2:    1 -> 3 -> 4 -> null
  dummy -> 1 -> 1 -> null
  current 指向第二个 1

步骤3: 比较 2 和 3,2 较小,取 list1 的 2
  list1:    1 -> 2 -> 4 -> null
  list2:    1 -> 3 -> 4 -> null
  dummy -> 1 -> 1 -> 2 -> null
  current 指向 2

步骤4: 比较 4 和 3,3 较小,取 list2 的 3
  list1:    1 -> 2 -> 4 -> null
  list2:      1 -> 3 -> 4 -> null
  dummy -> 1 -> 1 -> 2 -> 3 -> null
  current 指向 3

步骤5: 比较 4 和 4,相等,取 list1 的 4
  list1:        1 -> 2 -> 4 -> null
  list2:        1 -> 3 -> 4 -> null
  dummy -> 1 -> 1 -> 2 -> 3 -> 4 -> null
  current 指向 4

步骤6: list2 还有节点 4,直接接上去
  list1:        1 -> 2 -> 4 -> null
  list2:              1 -> 3 -> 4 -> null
  dummy -> 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> null

最终结果: dummy.next = 1 -> 1 -> 2 -> 3 -> 4 -> 4

算法步骤:

  1. 创建虚拟头节点 dummy,以及一个指针 current 指向 dummy
  2. 循环遍历两个链表:
    • 如果 list1 为空,将 current.next 指向 list2,跳出循环
    • 如果 list2 为空,将 current.next 指向 list1,跳出循环
    • 比较 list1.vallist2.val
      • 如果 list1.val <= list2.val,将 current.next 指向 list1list1 前进
      • 否则,将 current.next 指向 list2list2 前进
    • current 前进
  3. 返回 dummy.next

复杂度分析:

  • 时间复杂度: O(n + m),其中 n 和 m 分别是两个链表的长度,每个节点只被访问一次
  • 空间复杂度: O(1),只使用了常数个额外指针

代码实现:

Python 实现:

python 复制代码
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        """
        合并两个有序链表
        
        使用迭代法逐个比较节点值,将较小的节点接到新链表后面
        
        Args:
            list1: 第一个有序链表头节点
            list2: 第二个有序链表头节点
            
        Returns:
            合并后的链表头节点
        """
        dummy = ListNode(-1)  # 虚拟头节点
        current = dummy
        
        while list1 and list2:
            if list1.val <= list2.val:
                current.next = list1
                list1 = list1.next
            else:
                current.next = list2
                list2 = list2.next
            current = current.next
        
        current.next = list1 if list1 else list2
        
        return dummy.next

Java 实现:

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        /**
         * 合并两个有序链表
         *
         * 使用迭代法逐个比较节点值,将较小的节点接到新链表后面
         *
         * @param list1 第一个有序链表头节点
         * @param list2 第二个有序链表头节点
         * @return 合并后的链表头节点
         */
        ListNode dummy = new ListNode(-1);  // 虚拟头节点
        ListNode current = dummy;
        
        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                current.next = list1;
                list1 = list1.next;
            } else {
                current.next = list2;
                list2 = list2.next;
            }
            current = current.next;
        }
        
        current.next = (list1 != null) ? list1 : list2;
        
        return dummy.next;
    }
}

Rust 实现:

rust 复制代码
// Definition for singly-linked list.
// #[derive(PartialEq, Eq)]
// pub struct ListNode {
//     pub val: i32,
//     pub next: Option<Box<ListNode>>
// }
// 
// impl ListNode {
//     #[inline]
//     pub fn new(val: i32) -> Self {
//         ListNode {
//             val,
//             next: None
//         }
//     }
// }

impl Solution {
    pub fn merge_two_lists(list1: Option<Box<ListNode>>, list2: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
        match (list1, list2) {
            (None, None) => None,
            (Some(node), None) => Some(node),
            (None, Some(node)) => Some(node),
            (Some(mut node1), Some(mut node2)) => {
                if node1.val <= node2.val {
                    node1.next = Self::merge_two_lists(node1.next, Some(node2));
                    Some(node1)
                } else {
                    node2.next = Self::merge_two_lists(Some(node1), node2.next);
                    Some(node2)
                }
            }
        }
    }
}

方法二:递归法

思路:

递归的思路更加优雅。合并两个有序链表可以分解为:

  1. 比较两个链表的头节点
  2. 把较小的头节点与"合并剩余节点"的结果连接起来

也就是说,merge(list1, list2) 等于:

  • 如果 list1.val <= list2.val,那么 list1.next = merge(list1.next, list2)
  • 否则,list2.next = merge(list1, list2.next)

复杂度分析:

  • 时间复杂度: O(n + m),每个节点被访问一次
  • 空间复杂度: O(n + m),递归调用栈的深度

递归虽然代码简洁,但要注意递归深度的问题。当链表很长时,可能会导致栈溢出。

代码实现:

Python 实现:

python 复制代码
class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        """
        合并两个有序链表 - 递归解法
        
        递归地将较小的头节点与合并剩余节点的结果连接起来
        
        Args:
            list1: 第一个有序链表头节点
            list2: 第二个有序链表头节点
            
        Returns:
            合并后的链表头节点
        """
        # 基础情况:其中一个链表为空
        if not list1:
            return list2
        if not list2:
            return list1
        
        # 递归情况
        if list1.val <= list2.val:
            list1.next = self.mergeTwoLists(list1.next, list2)
            return list1
        else:
            list2.next = self.mergeTwoLists(list1, list2.next)
            return list2

Java 实现:

java 复制代码
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        /**
         * 合并两个有序链表 - 递归解法
         *
         * 递归地将较小的头节点与合并剩余节点的结果连接起来
         *
         * @param list1 第一个有序链表头节点
         * @param list2 第二个有序链表头节点
         * @return 合并后的链表头节点
         */
        // 基础情况:其中一个链表为空
        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;
        }
    }
}

总结与收获

知识点

  1. 链表操作:掌握链表节点的连接、移动和虚拟头节点的使用技巧
  2. 双指针技巧:使用两个指针同时遍历两个链表,比较并合并
  3. 递归思想:将复杂问题分解为相似的子问题,递归解决
  4. 虚拟头节点:简化边界条件处理,避免单独判断头节点

易错点

  1. 空链表处理:一定要考虑一个或两个链表为空的情况
  2. 指针移动:每次操作后要记得移动指针,否则会导致死循环
  3. 递归终止条件:递归一定要有正确的终止条件,否则会导致栈溢出
  4. 节点拼接:不要创建新节点,而是直接拼接原有节点

优化思路

本题的迭代解法已经是最优解,时间和空间复杂度都达到了最佳。递归解法虽然代码简洁,但需要注意递归深度的问题。

对于本题,可以考虑以下优化:

  1. 尾递归优化:某些编译器可以对尾递归进行优化,避免栈溢出
  2. 迭代转递归:如果喜欢递归的代码风格,可以先写递归版本,再手动转换为迭代
相关推荐
CCPC不拿奖不改名2 小时前
循环神经网络RNN:整数索引→稠密向量(嵌入层 / Embedding)详解
人工智能·python·rnn·深度学习·神经网络·自然语言处理·embedding
鹤入云霄2 小时前
基于Python的空气质量监测系统
python
viqjeee2 小时前
ALSA驱动开发流程
数据结构·驱动开发·b树
lagrahhn2 小时前
Java的RoundingMode舍入模式
java·开发语言·金融
鸽鸽程序猿2 小时前
【JavaEE】【SpringCloud】注册中心_nacos
java·spring cloud·java-ee
云上凯歌2 小时前
01 GB28181协议基础理解
java·开发语言
鹿角片ljp2 小时前
力扣7.整数反转-从基础到边界条件
算法·leetcode·职场和发展
java修仙传2 小时前
力扣hot100:前K个高频元素
算法·leetcode·职场和发展
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-考试系统DDD(领域驱动设计)实现步骤详解
java·数据库·人工智能·spring boot