21. 合并两个有序链表
21. 合并两个有序链表
题目来源
题目分析
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
题目难度
- 难度:简单
题目标签
- 标签:链表
题目限制
- 两个链表的节点数目范围是
[0, 50]
-100 <= Node.val <= 100
解题思路
思路:
-
问题定义:
- 本题要求将两个已经排好序的链表合并成一个新的有序链表。
-
核心算法:
- 迭代法:通过创建一个哨兵节点
head
和一个当前指针cur
,逐一比较两个链表的节点值,将较小的节点接到cur
后面,最后将剩余的节点拼接到新链表的末尾。 - 合并两个链表中较小的节点,并将其余的部分递归合并。
- 迭代法:通过创建一个哨兵节点
-
关键步骤:
- 迭代过程中,始终保持链表的有序性。
- 过程的每一步都处理当前最小的节点,并将剩余的部分继续合并。
核心算法步骤
迭代法
- 创建哨兵节点:初始化一个哨兵节点
head
,并用指针cur
指向它。 - 遍历比较:遍历两个链表,比较
list1
和list2
当前节点的值,将较小值的节点接到cur
后面,并移动相应链表的指针。 - 拼接剩余部分:当其中一个链表遍历完后,直接将另一个链表剩余部分接到新链表的末尾。
代码实现
以下是迭代法的 Java 代码实现:
/**
* 21. 合并两个有序链表
* @param list1 链表1
* @param list2 链表2
* @return 合并后的链表
* @apiNote 迭代法,时间复杂度O(n),空间复杂度O(1)
*/
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode head = new ListNode();
ListNode cur = head;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
cur.next = list1;
list1 = list1.next;
} else {
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
cur.next = list1 != null ? list1 : list2;
return head.next;
}
代码解读
- 哨兵节点:哨兵节点
head
只是为了简化链表操作,最后返回head.next
作为新链表的头节点。 - 当前指针:
cur
用来拼接较小值的节点并移动指针,以保证链表的有序性。 - 剩余部分:当其中一个链表遍历结束后,直接将另一个链表的剩余部分拼接到新链表的末尾。
性能分析
- 时间复杂度:
O(n)
,n
为两个链表的节点数之和,每个节点只被遍历一次。 - 空间复杂度:
O(1)
,仅使用了常数级的辅助空间,没有额外分配新节点。
复杂度结果
测试用例
你可以使用以下测试用例来验证代码的正确性:
// 测试用例1
ListNode list1 = new ListNode(1, new ListNode(2, new ListNode(4)));
ListNode list2 = new ListNode(1, new ListNode(3, new ListNode(4)));
ListNode mergedList1 = mergeTwoLists(list1, list2);
printList(mergedList1); // 输出: 1 -> 1 -> 2 -> 3 -> 4 -> 4
// 测试用例2
ListNode list3 = null;
ListNode list4 = new ListNode(0);
ListNode mergedList2 = mergeTwoLists(list3, list4);
printList(mergedList2); // 输出: 0
扩展讨论
优化写法
- 递归法:可以通过递归的方式实现合并两个有序链表,代码更为简洁,但空间复杂度为
O(n)
。
public ListNode mergeTwoListsRecursive(ListNode list1, ListNode list2) {
if (list1 == null) return list2;
if (list2 == null) return list1;
if (list1.val < list2.val) {
list1.next = mergeTwoListsRecursive(list1.next, list2);
return list1;
} else {
list2.next = mergeTwoListsRecursive(list1, list2.next);
return list2;
}
}
其他实现
- 双指针法:在不使用哨兵节点的情况下,可以直接操作
list1
和list2
,但需要处理初始条件和特殊情况,代码会稍显复杂。
总结
这道题目考察了链表的基本操作,通过迭代和递归两种方法都能有效地合并两个有序链表。迭代法更适合处理链表较长的情况,而递归法则在代码上更加简洁。