【Java 实现】合并两个升序链表(Leetcode21题):一步步讲解

链表是计算机科学中非常重要的一种数据结构,它是一种线性数据结构,由多个节点按顺序连接而成。每个节点包含两个部分:一个存储数据的字段和一个指向下一个节点的引用。在处理链表时,操作往往涉及到插入、删除、查找以及合并等任务。

今天,我们来讲解一道leetcode上的经典的链表题——合并两个有序链表,并用 Java 来实现。让我们一步步深入了解这道题目以及链表的相关知识。

一、问题分析

题目要求我们将两个升序链表合并成一个新的升序链表,并返回合并后的链表。**重要点:**两个链表是升序排列的,因此我们可以利用这一特性,通过逐步比较两个链表的节点,将它们合并成一个有序的链表。

示例

示例 1:

输入:l1 = [1, 2, 4], l2 = [1, 3, 4]
输出:[1, 1, 2, 3, 4, 4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

从这些示例中,我们可以看到:

  • 如果一个链表为空,另一个链表直接作为结果返回。

  • 如果两个链表的节点数目不相等,我们需要将较短的链表与较长的链表合并。

  • 最终合并后,结果链表依然需要是有序的。

二、链表基础

1. 链表的结构

链表的每个节点包含两个字段:

  • val):节点存储的数据。

  • 下一个节点的引用next):指向链表中的下一个节点。

链表可以通过以下方式定义:

class ListNode {
    int val;
    ListNode next;
    ListNode(int val) {
        this.val = val;
        this.next = null;
    }
}

2. 链表的基本操作

在处理链表时,常见的操作包括:

  • 插入节点:将新节点插入到链表中。

  • 删除节点:删除链表中的节点。

  • 遍历链表:从头节点开始,遍历整个链表。

  • 合并链表:将两个链表合并成一个。

三、算法设计

合并两个升序链表时,我们可以使用双指针法,通过两个指针分别指向两个链表的头节点,并比较它们的值,逐步合并成一个新链表。

步骤:

  1. 创建一个虚拟节点(dummy),它帮助我们简化代码,不需要特殊处理链表的头节点。

  2. 使用两个指针 p1p2,分别指向 l1l2 的当前节点。

  3. 比较 p1p2 当前节点的值,选择较小的一个节点,并将该节点加入到结果链表中。

  4. 继续移动指针,直到遍历完两个链表。

  5. 如果一个链表遍历完了,而另一个链表还有剩余节点,直接将剩余的节点接到结果链表的末尾。

四、Java 代码实现

class ListNode {
    int val;
    ListNode next;
    ListNode(int val) {
        this.val = val;
        this.next = null;
    }
}

public class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 创建一个虚拟头节点
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        
        // 两个指针p1和p2分别指向l1和l2
        ListNode p1 = l1;
        ListNode p2 = l2;
        
        // 遍历两个链表
        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; // 移动current指针
        }
        
        // 如果l1还有剩余
        if (p1 != null) {
            current.next = p1;
        }
        
        // 如果l2还有剩余
        if (p2 != null) {
            current.next = p2;
        }
        
        // 返回合并后的链表(去掉虚拟头节点)
        return dummy.next;
    }
}

解释:

  • 我们使用了一个虚拟头节点 dummy 来简化代码,这样我们可以直接从 dummy.next 开始返回合并后的链表,而不需要单独处理头节点。

  • p1p2 分别遍历 l1l2,通过比较它们的节点值来决定将哪个节点添加到 current 后面。

  • p1p2 其中一个链表遍历结束后,我们直接将另一个链表剩余的部分连接到结果链表的末尾。

五、时间复杂度与空间复杂度

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

  • 空间复杂度:我们只用了一个额外的指针来帮助合并,空间复杂度为 O(1)

六、总结

合并两个升序链表是链表相关题目中的经典问题,通过使用双指针法,我们能够高效地将两个有序链表合并为一个新的有序链表。我们详细介绍了链表的基本概念、问题的思路和如何实现这一过程。

希望这篇文章能够帮助你更好地理解链表操作的基本原理和在实际编程中的应用!