Problem: 21. 合并两个有序链表
题目:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(M + N)
- 空间复杂度:O(1)
整体思路
这段代码旨在解决一个基础且经典的链表问题:合并两个有序链表 (Merge Two Sorted Lists)。问题要求将两个已按升序排列的链表合并为一个新的、仍然保持升序的链表。
该算法采用了一种非常标准和高效的 “迭代法”,通过一个 “虚拟头节点(Dummy Node)” 技巧来简化代码逻辑。
-
虚拟头节点(Dummy Node)的作用:
- 算法首先创建了一个名为
dummy
的新节点。这个节点本身不存储任何有效数据,它的主要作用是作为新合并链表的哨兵或占位符。 - 优势:使用
dummy
节点可以避免处理“新链表为空”的边界情况。我们始终可以安全地向dummy.next
添加第一个有效节点,而无需编写if (newList == null)
这样的判断。最后,我们只需返回dummy.next
即可得到真正的新链表头。 - 一个
tail
指针被初始化为dummy
,它将始终指向新合并链表的尾部,用于方便地追加新节点。
- 算法首先创建了一个名为
-
迭代合并:
- 算法使用一个
while
循环,只要两个输入链表list1
和list2
都不为空,就持续进行比较和合并。 - 在循环的每一步,比较
list1.val
和list2.val
的大小:- 如果
list1.val < list2.val
:说明list1
的当前节点值更小,应该被先加入到新链表中。于是,将tail.next
指向list1
,然后将list1
指针向前移动一步 (list1 = list1.next
)。 - 否则 (
list1.val >= list2.val
):说明list2
的当前节点值更小或相等,它应该被加入新链表。将tail.next
指向list2
,然后移动list2
指针。
- 如果
- 在将一个节点链接到新链表后,必须将
tail
指针也向前移动 (tail = tail.next
),使其始终指向新链表的最后一个节点,为下一次追加做准备。
- 算法使用一个
-
处理剩余部分:
- 当
while
循环结束时,意味着list1
或list2
(或两者都)已经为空。 - 此时,最多只有一个链表还有剩余的节点。由于输入链表本身就是有序的,这个剩余的部分也必然是有序的,并且其所有节点的值都大于或等于已合并链表中的所有值。
- 因此,我们无需再逐个比较,直接将
tail.next
指向那个非空的剩余链表即可。 - 代码
tail.next = list1 != null ? list1 : list2;
巧妙地实现了这一逻辑。
- 当
-
返回结果:
- 最后,返回
dummy.next
,它指向新合并链表的真正头节点。
- 最后,返回
完整代码
/*** 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 {/*** 将两个升序链表合并为一个新的升序链表。* @param list1 第一个有序链表的头节点* @param list2 第二个有序链表的头节点* @return 合并后新链表的头节点*/public ListNode mergeTwoLists(ListNode list1, ListNode list2) {// 创建一个虚拟头节点(dummy node),作为新链表的哨兵。ListNode dummy = new ListNode();// tail 指针用于追踪新链表的尾部,方便追加节点。ListNode tail = dummy;// 步骤 1: 迭代比较和合并,直到其中一个链表为空。while (list1 != null && list2 != null) {// 比较两个链表当前节点的值if (list1.val < list2.val) {// 如果 list1 的值更小,将 list1 的当前节点链接到新链表尾部tail.next = list1;// 移动 list1 的指针到下一个节点list1 = list1.next;} else {// 否则,将 list2 的当前节点链接到新链表尾部tail.next = list2;// 移动 list2 的指针到下一个节点list2 = list2.next;}// 无论哪个节点被链接,都要将 tail 指针移动到新链表的当前末尾tail = tail.next;}// 步骤 2: 处理剩余的链表部分。// 循环结束后,最多只有一个链表还有剩余节点。// 直接将新链表的尾部链接到这个剩余链表的头部。tail.next = (list1 != null) ? list1 : list2;// 步骤 3: 返回虚拟头节点的下一个节点,即新链表的真正头节点。return dummy.next;}
}
时空复杂度
时间复杂度:O(M + N)
- 循环分析:
- 算法的核心是
while
循环。在每次迭代中,list1
或list2
中的一个指针会向前移动一步。 - 这个循环会一直持续到其中一个链表被完全遍历完。
- 设
list1
的长度为M
,list2
的长度为N
。while
循环的总迭代次数等于M
和N
中较小者的长度。 - 处理剩余部分的操作是 O(1)。
- 总的来说,算法需要遍历两个链表的所有节点恰好一次。
- 算法的核心是
综合分析:
算法的时间复杂度与两个链表的总长度成线性关系,即 O(M + N)。
空间复杂度:O(1)
- 主要存储开销:该算法是在原地重新链接(re-wire)输入链表的节点,并没有为新链表创建新的节点。
- 辅助变量:只创建了
dummy
和tail
两个额外的指针变量。这些变量的数量是固定的,与链表长度无关。
综合分析:
算法所需的额外辅助空间是常数级别的。因此,其空间复杂度为 O(1)。这是一个空间效率最优的迭代解法。
【LeetCode 热题 100】21. 合并两个有序链表——(解法二)递归法
参考灵神