Skip to content

题目链接 · 灵神原题解(署名来源)

方法一:迭代(尾插法)

前置知识

思路

创建一个哨兵节点,作为合并后的新链表头节点的前一个节点。这样可以避免单独处理头节点,也无需特判链表为空的情况,从而简化代码。

比较 list1list2 的节点值,如果 list1 的节点值小,则把 list1 加到新链表的末尾,然后把 list1 替换成它的下一个节点。如果 list2 的节点值小则同理。如果两个节点值一样,那么把谁加到新链表的末尾都是一样的,不妨规定把 list2 加到新链表末尾。

重复上述过程,直到其中一个链表为空。

循环结束后,其中一个链表可能还有剩余的节点,将剩余部分加到新链表的末尾。

最后,返回新链表的头节点,即哨兵节点的下一个节点。

python
class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        cur = dummy = ListNode()  # 用哨兵节点简化代码逻辑
        while list1 and list2:
            if list1.val < list2.val:
                cur.next = list1  # 把 list1 加到新链表中
                list1 = list1.next
            else:  # 注:相等的情况加哪个节点都是可以的
                cur.next = list2  # 把 list2 加到新链表中
                list2 = list2.next
            cur = cur.next
        cur.next = list1 or list2  # 拼接剩余链表
        return dummy.next
cpp
// C++ 版待补充
cpp
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode dummy; // 用哨兵节点简化代码逻辑
        ListNode* cur = &dummy; // cur 指向新链表的末尾
        while (list1 && list2) {
            if (list1->val < list2->val) {
                cur->next = list1; // 把 list1 加到新链表中
                list1 = list1->next;
            } else { // 注:相等的情况加哪个节点都是可以的
                cur->next = list2; // 把 list2 加到新链表中
                list2 = list2->next;
            }
            cur = cur->next;
        }
        cur->next = list1 ? list1 : list2; // 拼接剩余链表
        return dummy.next;
    }
};

复杂度分析

  • 时间复杂度:O(n+m),其中 nlist1 的长度,mlist2 的长度。
  • 空间复杂度:O(1)。仅用到若干额外变量。

方法二:递归(头插法)

前置知识

如何理解递归?计算机是怎么执行递归的?【基础算法精讲 09】

思路

直接把 mergeTwoLists 当作递归函数:

  • 递归边界:如果其中一个链表为空,直接返回另一个链表作为合并后的结果。
  • 如果两个链表都不为空,则比较两个链表当前节点的值,选择较小的节点插在前面。
    • 如果 list1 的节点值更小,那么取出 list1,递归调用 mergeTwoLists(list1.next, list2),拿到递归返回的链表,把 list1 插在前面。
    • 如果 list2 的节点值更小,那么取出 list2,递归调用 mergeTwoLists(list1, list2.next),拿到递归返回的链表,把 list2 插在前面。
python
class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        if list1 is None: return list2  # 注:如果都为空则返回空
        if list2 is None: return list1
        if list1.val < list2.val:
            list1.next = self.mergeTwoLists(list1.next, list2)
            return list1
        list2.next = self.mergeTwoLists(list1, list2.next)
        return list2
cpp
// C++ 版待补充
cpp
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;
        }
        list2->next = mergeTwoLists(list1, list2->next);
        return list2;
    }
};

复杂度分析

  • 时间复杂度:O(n+m),其中 nlist1 的长度,mlist2 的长度。
  • 空间复杂度:O(n+m)。递归需要 O(n+m) 的栈空间。

思考题

如果只保留两个有序链表中的相同元素(求交集),要怎么做?注意,如果第一个链表有 31,第二个链表有 21,那么答案链表中有 21

欢迎在评论区发表你的思路/代码。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

本文整理自灵茶山艾府(endlesscheng)的公开内容,仅供个人学习使用

本站仅供个人学习使用,请勿外传