一、题目描述
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = [] 输出:[]
示例 3:
输入:l1 = [], l2 = [0] 输出:[0]
提示:
- 两个链表的节点数目范围是
[0, 50]
-100 <= Node.val <= 100
l1
和l2
均按 非递减顺序 排列
二、解题思路
- 创建一个新的虚拟头节点,这个节点不会包含任何实际的值,它的目的是为了简化边界条件处理,比如在合并完成后直接返回头节点的下一个节点作为新链表的头节点。
- 创建一个当前指针(current),初始指向虚拟头节点。
- 使用两个指针分别指向两个链表的当前节点,分别为
p1
和p2
,初始时p1
指向list1
的头节点,p2
指向list2
的头节点。 - 进入一个循环,循环条件是
p1
和p2
都不为空。 - 在每次循环中,比较
p1
和p2
的值,将较小的节点连接到current
后面,然后将对应的指针向前移动一位。 - 如果其中一个链表的指针已经移动到末尾,将另一个链表剩余的所有节点连接到
current
后面。 - 当两个链表的所有节点都处理完毕后,返回虚拟头节点的下一个节点,即新链表的头节点。
三、具体代码
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// 创建虚拟头节点
ListNode dummy = new ListNode(0);
// 当前指针,初始指向虚拟头节点
ListNode current = dummy;
// 初始化两个链表的指针
ListNode p1 = list1, p2 = list2;
// 当两个链表都不为空时循环
while (p1 != null && p2 != null) {
// 比较两个节点的值,选择较小的节点
if (p1.val < p2.val) {
current.next = p1;
p1 = p1.next;
} else {
current.next = p2;
p2 = p2.next;
}
// 移动当前指针
current = current.next;
}
// 如果一个链表为空,将另一个链表的剩余部分连接到当前链表
if (p1 != null) {
current.next = p1;
} else if (p2 != null) {
current.next = p2;
}
// 返回新链表的头节点
return dummy.next;
}
}
四、时间复杂度与空间复杂度
这段代码的时间复杂度是 O(m + n),空间复杂度是 O(1)。
1. 时间复杂度
- 代码中的循环会遍历两个链表中的所有元素,直到其中一个链表的所有元素都被处理完毕。
- 在最坏的情况下,即两个链表长度相等,循环会执行大约
m + n
次,其中m
是list1
的长度,n
是list2
的长度。 - 因此,时间复杂度是 O(m + n)。
2. 空间复杂度
- 创建了一个虚拟头节点
dummy
,它是一个常数级别的额外空间。 current
指针在循环中被重复使用,指向新链表的当前节点,这不会增加额外的空间复杂度。- 除了输入链表外,没有使用额外的数据结构来存储链表元素,所以空间复杂度是 O(1),即常数空间复杂度。
五、总结知识点
1. 链表(Linked List):
- 链表是一种线性数据结构,其中的元素(节点)按顺序链接在一起。
- 每个节点包含数据部分(
val
)和指向下一个节点的指针(next
)。
2. 虚拟头节点(Dummy Node):
- 在处理链表问题时,有时会创建一个虚拟头节点,它的目的是简化边界条件的处理,如在合并链表时不需要特别处理头节点。
3. 指针操作:
- 使用指针(
p1
和p2
)遍历和操作链表。 - 在循环中,指针用于比较和选择两个链表中的较小节点,并更新指针以指向下一个节点。
4. 条件语句(Conditional Statement):
- 使用
if-else
语句来决定哪个节点应该成为新链表的一部分。
5. 循环(Loop):
- 使用
while
循环来遍历两个链表,直到其中一个链表的所有元素都被处理。
6. 链表的连接:
- 在循环中,通过更新
current.next
来连接两个链表的节点,形成新的链表。
7. 边界条件处理:
- 在循环结束后,需要检查两个链表是否还有剩余的节点,如果有,需要将剩余的节点连接到新链表的末尾。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。