借助虚拟头节点,解决力扣算法“合并两个有序链表”

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

提示:

  • 两个链表的节点数目范围是 [0, 50]

  • -100 <= Node.val <= 100

  • l1 和 l2 均按 非递减顺序 排列

image


核心思想:

要将两个升序链表合并为一个新的升序链表,可以通过比较两个链表的当前节点,将较小的节点接到新链表上。当一个链表遍历完毕后,将另一个链表直接接到新链表的末尾。最后,返回新链表的头节点。

算法步骤:

  1. 创建一个虚拟头节点 dummyHead,用于简化边界情况的处理。

  2. 初始化一个当前节点 res 指向 dummyHead,用于构建新链表。

  3. 比较两个链表的当前节点,将较小的节点接到新链表上,并将相应的链表指针前移一位。

  4. 重复步骤3,直到一个链表遍历完毕。

  5. 将未遍历完的链表直接接到新链表的末尾。

  6. 返回 dummyHead.next,即新链表的头节点。

针对我给的算法步骤,可能有人会疑惑,为什么要创建一个虚拟头节点呢?

因为创建一个虚拟头节点(也称为哑节点或虚拟节点)在合并链表的算法中有很多好处:

  • 简化操作:使用虚拟头节点可以简化对新链表头节点的操作。在合并过程中,我们不需要单独处理新链表头节点的初始化,因为虚拟头节点的下一个节点自然就是新链表的头节点。

  • 统一处理:虚拟头节点使得对链表的头部和非头部节点的处理统一。在合并过程中,我们不需要对特殊情况(如其中一个链表为空)进行额外的判断和处理。

  • 避免空指针:如果两个输入链表中有一个是空的,使用虚拟头节点可以避免在合并过程中出现空指针异常。因为虚拟头节点始终存在,我们可以安全地返回 dummyHead.next 作为结果。

  • 代码清晰:虚拟头节点使得算法的逻辑更加清晰和易于理解。它隐藏了链表操作中一些复杂的边界情况,使得代码更加简洁和直观。

  • 兼容性:在一些链表操作中,例如反转链表,虚拟头节点可以提供与非空链表相同的操作接口,使得算法可以适用于更广泛的情况。

而在这个题目中,虚拟头节点 dummyHead 在函数内部创建,不会返回给调用者。它仅用于辅助构建新链表,最终返回的是 dummyHead.next,即新链表的实际头节点。这样做可以避免在函数外部处理边界情况,使函数的接口更加简洁。

复杂度分析:

  • 时间复杂度是 O (m + n),其中 m 和 n 分别是两个链表的长度,因为我们最多遍历每个链表一次。

  • 空间复杂度是 O (1),因为我们只使用了常数级别的额外空间。

我的 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) {
        // 特殊情况:两个链表都是空链表
        if(list1==null && list2==null){
            return null;
        }

        // 创建一个虚拟头节点
        ListNode dummyHead = new ListNode(0); 
        ListNode res = dummyHead;

        // 遍历两个链表,直到其中一个链表为空
        while(list1!=null && list2!=null){
            if(list1.val < list2.val){
                res.next = list1;
                list1 = list1.next;
            }else{
                res.next = list2;
                list2 = list2.next;
            }
            res = res.next; // 注意!每次加入新结点后都要向后移动一个位置
        }

        // 将未遍历完的链表直接接到新链表的末尾 
        if(list1 == null){
            res.next = list2;
        }else{
            res.next = list1;
        }

        // 返回新链表的头节点
        return dummyHead.next;
    }
}
posted @ 2025-07-29 00:18  junjunyi  阅读(36)  评论(0)    收藏  举报