【LeetCode 热题 100】21. 合并两个有序链表——(解法一)迭代法

Problem: 21. 合并两个有序链表
题目:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(M + N)
    • 空间复杂度:O(1)

整体思路

这段代码旨在解决一个基础且经典的链表问题:合并两个有序链表 (Merge Two Sorted Lists)。问题要求将两个已按升序排列的链表合并为一个新的、仍然保持升序的链表。

该算法采用了一种非常标准和高效的 “迭代法”,通过一个 “虚拟头节点(Dummy Node)” 技巧来简化代码逻辑。

  1. 虚拟头节点(Dummy Node)的作用

    • 算法首先创建了一个名为 dummy 的新节点。这个节点本身不存储任何有效数据,它的主要作用是作为新合并链表的哨兵占位符
    • 优势:使用 dummy 节点可以避免处理“新链表为空”的边界情况。我们始终可以安全地向 dummy.next 添加第一个有效节点,而无需编写 if (newList == null) 这样的判断。最后,我们只需返回 dummy.next 即可得到真正的新链表头。
    • 一个 tail 指针被初始化为 dummy,它将始终指向新合并链表的尾部,用于方便地追加新节点。
  2. 迭代合并

    • 算法使用一个 while 循环,只要两个输入链表 list1list2不为空,就持续进行比较和合并。
    • 在循环的每一步,比较 list1.vallist2.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),使其始终指向新链表的最后一个节点,为下一次追加做准备。
  3. 处理剩余部分

    • while 循环结束时,意味着 list1list2(或两者都)已经为空。
    • 此时,最多只有一个链表还有剩余的节点。由于输入链表本身就是有序的,这个剩余的部分也必然是有序的,并且其所有节点的值都大于或等于已合并链表中的所有值。
    • 因此,我们无需再逐个比较,直接将 tail.next 指向那个非空的剩余链表即可。
    • 代码 tail.next = list1 != null ? list1 : list2; 巧妙地实现了这一逻辑。
  4. 返回结果

    • 最后,返回 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)

  1. 循环分析
    • 算法的核心是 while 循环。在每次迭代中,list1list2 中的一个指针会向前移动一步。
    • 这个循环会一直持续到其中一个链表被完全遍历完。
    • list1 的长度为 Mlist2 的长度为 Nwhile 循环的总迭代次数等于 MN 中较小者的长度。
    • 处理剩余部分的操作是 O(1)。
    • 总的来说,算法需要遍历两个链表的所有节点恰好一次。

综合分析
算法的时间复杂度与两个链表的总长度成线性关系,即 O(M + N)

空间复杂度:O(1)

  1. 主要存储开销:该算法是在原地重新链接(re-wire)输入链表的节点,并没有为新链表创建新的节点。
  2. 辅助变量:只创建了 dummytail 两个额外的指针变量。这些变量的数量是固定的,与链表长度无关。

综合分析
算法所需的额外辅助空间是常数级别的。因此,其空间复杂度为 O(1)。这是一个空间效率最优的迭代解法。

参考灵神

你可能感兴趣的:(LeetCode,leetcode,链表,算法,java)