hot100_合并两个有序链表

合并两个有序链表题解

hot100_合并两个有序链表

1. 题目

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

示例 1:

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

示例 2:

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

示例 3:

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


2. 题解

2.1. 递归

2.1.1. 核心思想

  1. 递归终止条件(基线条件) 如果其中一个链表是空,直接返回另一个链表。

子问题最小情况:没有节点需要合并,直接返回剩下的链表。

if(list1 == nullptr) return list2;
if(list2 == nullptr) return list1;
  1. 问题分解 比较两个链表头结点:选值更小的节点作为当前结果的头。 假设 list1->val < list2->val,那么list1就是当前这一步的头节点。 但是list1的next应该是什么? list1->next 等于:合并 list1->next 和 list2 这两个子链表得到的结果。
list1->next = mergeTwoLists(list1->next, list2);
return list1;
  • 原问题:合并 list1, list2
  • 子问题:合并 list1->next, list2 把子问题的返回结果,接在当前节点后面,然后返回当前节点。
  1. 归并(回溯) 递归一层一层往下拆,直到触发终止条件; 然后逐层向上返回已经合并好的链表,上层拿到下层合并完成的链表,接到自己的 next。
merge(l1,l2)={l2,l1=nulll1,l2=nulll1, 其中 l1.next=merge(l1.next,l2),l1.val<l2.vall2, 其中 l2.next=merge(l1,l2.next),其他merge(l1,l2)= \begin{cases} l2,& l1=\text{null}\\ l1,& l2=\text{null}\\ l1,\text{ 其中 }l1.next=merge(l1.next,l2),& l1.val<l2.val\\ l2,\text{ 其中 }l2.next=merge(l1,l2.next),& \text{其他} \end{cases}

2.1.2. 代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == nullptr) return list2;
        if(list2 == nullptr) return list1;
        if(list1->val < list2->val) {
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        } else {
            list2->next = mergeTwoLists(list1, list2->next);
            return list2;
        }
    }
};

2.1.3. 复杂度

时间复杂度:O(m+n)O(m+n),m、n 分别为两条链表长度,每个节点只会被处理一次。 空间复杂度:O(m+n)O(m+n),消耗来自递归调用栈;链表节点数量很大时,存在栈溢出风险。

2.2. 迭代

2.2.1. 核心思想

核心:手动维护新链表的头指针 head、尾指针 tail,循环从两条有序链表挑选更小节点接到尾部,最后拼接剩余链表。 没有虚拟哑节点,需要专门处理第一个节点的特殊情况。

2.2.2. 代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == nullptr) return list2;
        if(list2 == nullptr) return list1;
        ListNode* head = nullptr, * tail = nullptr;
        ListNode* p1 = list1, * p2 = list2;
        while(p1 && p2) {
            if(p1->val < p2->val) {
                if(head == nullptr) {
                    head = tail = p1;
                } else {
                    tail->next = p1;
                    tail = p1;
                }
                p1 = p1->next;
            } else {
                if(head == nullptr) {
                    head = tail = p2;
                } else {
                    tail->next = p2;
                    tail = p2;
                }
                p2 = p2->next;
            }
        }
        if(p1) tail->next = p1;
        if(p2) tail->next = p2;
        return head;
    }
};

带虚拟头节点代码

ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
    ListNode dummy;
    ListNode* cur = &dummy;
    while(list1 && list2) {
        if(list1->val < list2->val) {
            cur->next = list1;
            list1 = list1->next;
        } else {
            cur->next = list2;
            list2 = list2->next;
        }
        cur = cur->next;
    }
    cur->next = list1 ? list1 : list2;
    return dummy.next;
}

2.2.3. 复杂度

时间复杂度:O(m+n)O(m+n),m、n 分别为两条链表长度,每个节点只会被处理一次。 空间复杂度:O(1)O(1),只使用指针,不新建节点,原地修改链表指针。

2.3. 两种算法对比

实现方式时间空间核心特点优缺点
迭代‑无哨兵 (head+tail)O(m+n)O(1)手动维护 head、tail,特殊处理首节点,需前置判空代码冗余,容易空指针;不推荐面试写
迭代‑dummy 虚拟头结点O(m+n)O(1)哨兵抹平头节点边界,逻辑统一代码简洁,边界友好,首选
递归O(m+n)O(m+n)分解子问题,回溯拼接代码短;长链表会栈溢出

3. 21. 合并两个有序链表 - 力扣(LeetCode)

chengzi