LeetCode - [21] 合并两个有序链表|刷题打卡

218 阅读2分钟

题目描述:

原题地址

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

示例一

image.png

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

示例二

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

示例三

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

提示

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1l2 均按 非递减顺序 排列

思路分析

递归

什么是递归呢?函数在运行时调用自己,这个函数就叫递归函数,调用的过程叫做递归。 比如定义函数 f(x)=x+f(x1)f(x)=x+f(x-1)
如果代入 f(2)f(2)

  • 返回 2+f(1)2+f(1)
  • 调用 f(1)f(1)
  • 返回 1+f(0)1+f(0)
  • 调用 f(0)f(0)
  • 返回 0+f(1)0+f(-1)
  • ...... 这时程序会无休止地运行下去,直到崩溃。 所以递归函数必须要有终止条件,否则会出错

回归本题
终止条件:两条链表分别名为 l1l2,当 l1 为空或 l2 为空时结束 如何递归:如果 l1val 值更小,则将 l1.next 与排序好的链表头相接,l2同理

复杂度分析

如何计算递归的时间复杂度和空间复杂度呢?
力扣对此进行了 详细介绍 ,其中时间复杂度可以这样计算:

给出一个递归算法,其时间复杂度 O(T)\mathcal{O}(T) 通常是递归调用的数量(记作 R{R}) 和计算的时间复杂度的乘积(表示为 O(s)\mathcal{O}(s))的乘积:O(T)=RO(s){\mathcal{O}(T) = R * \mathcal{O}(s)}

时间复杂度:O(m+n){\mathcal{O}}(m + n)

m,n 为 l1 和 l2的元素个数。递归函数每次去掉一个元素,直到两个链表都为空,因此需要调用 R=O(m+n)R=\mathcal{O}(m + n)次。而在递归函数中我们只进行了 next指针的赋值操作,复杂度为 O(1)\mathcal{O}(1),故递归的总时间复杂度为 O(T)=RO(1)=O(m+n){\mathcal{O}(T) = R * \mathcal{O}(1)}={\mathcal{O}}(m + n)

空间复杂度:O(m+n){\mathcal{O}}(m + n)
对于递归调用 self.mergeTwoLists(),当它遇到终止条件准备回溯时,已经递归调用了 m+n次,使用了 m+n 个栈帧,故最后的空间复杂度为 O(m+n){\mathcal{O}}(m + n)

AC代码

class Solution {
    fun mergeTwoLists(l1: ListNode?, l2: ListNode?): ListNode? {
        // 两条链表分别名为 l1 和 l2,当 l1 为空或 l2 为空时结束
        if (l1 == null) return l2
        if (l2 == null) return l1

        // 如果 l1 的 val 值更小,则将 l1.next 与排序好的链表头相接,l2 同理
        if (l1.`val` < l2.`val`) {
            l1.next = mergeTwoLists(l1.next, l2)
            // 每一层调用都返回排序好的链表头
            return l1
        } else {
            l2.next = mergeTwoLists(l1, l2.next)
            return l2
        }
    }
}

参考

官解 合并两个有序链表
一看就会,一写就废?详解递归

总结

递归解法总是给人一种 只可意会不可言传 的感觉,代码一看就懂,自己动手一写就呆住了,很难受。究其原因,一是我们练习不够,二是理解不够。

顺便附一个大佬的评论

其实递归就是程序内部维护了一个栈。这个题就是每次都把最小值压入栈,最后出栈的时候,将所有数连在一起就可以了。说白了,就是用一个栈维护了顺序。最后的连接,当然是小的连小的,所以l1 小,就连到 l1,l2 小就连到 l2,最后先返回的,就是最小的头结点。

再接再厉。

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情