题目信息
- 题目编号: 21
- 题目名称: 合并两个有序链表
- 标签: 链表, 递归
- 难度: 简单
- 题目链接 : LeetCode 21
题目描述
将两个升序排列的链表 list1 和 list2 合并为一个新的升序链表。新链表应该通过拼接前两个链表的节点来创建。
返回合并后的链表头节点。
示例
示例 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)来简化边界条件的处理,然后遍历两个链表,将较小的节点依次接到新链表后面。
关键点:
- 使用虚拟头节点
dummy,这样不需要单独处理头节点 - 使用指针
current追踪新链表的当前位置 - 每次比较
list1和list2当前节点的值,将较小的节点接到current后面 - 当其中一个链表遍历完后,直接将另一个链表接上去
根据一个简单示例,通过图示展示思路:
以 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
算法步骤:
- 创建虚拟头节点
dummy,以及一个指针current指向dummy - 循环遍历两个链表:
- 如果
list1为空,将current.next指向list2,跳出循环 - 如果
list2为空,将current.next指向list1,跳出循环 - 比较
list1.val和list2.val:- 如果
list1.val <= list2.val,将current.next指向list1,list1前进 - 否则,将
current.next指向list2,list2前进
- 如果
current前进
- 如果
- 返回
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)
}
}
}
}
}
方法二:递归法
思路:
递归的思路更加优雅。合并两个有序链表可以分解为:
- 比较两个链表的头节点
- 把较小的头节点与"合并剩余节点"的结果连接起来
也就是说,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;
}
}
}
总结与收获
知识点
- 链表操作:掌握链表节点的连接、移动和虚拟头节点的使用技巧
- 双指针技巧:使用两个指针同时遍历两个链表,比较并合并
- 递归思想:将复杂问题分解为相似的子问题,递归解决
- 虚拟头节点:简化边界条件处理,避免单独判断头节点
易错点
- 空链表处理:一定要考虑一个或两个链表为空的情况
- 指针移动:每次操作后要记得移动指针,否则会导致死循环
- 递归终止条件:递归一定要有正确的终止条件,否则会导致栈溢出
- 节点拼接:不要创建新节点,而是直接拼接原有节点
优化思路
本题的迭代解法已经是最优解,时间和空间复杂度都达到了最佳。递归解法虽然代码简洁,但需要注意递归深度的问题。
对于本题,可以考虑以下优化:
- 尾递归优化:某些编译器可以对尾递归进行优化,避免栈溢出
- 迭代转递归:如果喜欢递归的代码风格,可以先写递归版本,再手动转换为迭代