【Golang】LeetCode 300. 最长递增子序列

300. 最长递增子序列

题目描述

思路

解决这道题有两种思路,一种是动态规划,一种是二分查找,我们分别用两种思路来解决这道题目。

思路 1:动态规划

首先,这道题目可以使用动态规划来解决。这道题使用动态规划的方法比较有意思,我们维护一个数组dp用来记录从下标0开始到位置i这个区间内的最长递增子序列的长度是多少。具体来说,dp[i] = 4指的就是0...i这个区间内,最长递增子序列的长度为4。那么我们应该如何维护dp数组呢?答案是通过一个双层循环来进行维护。

具体来说,我们在最外层循环使用i来遍历整个数组,在第二层循环使用j来遍历0...i。如果满足nums[i] > nums[j],就说明nums[j], nums[i]构成一个单调递增序列,这个序列的长度至少为2(答案至少是1,因为单个元素本身就是长度为1的单调子序列)。由此我们可以得到状态转移方程,dp[i] = max(dp[j] + 1, dp[i])。由于我们是从前往后顺序对nums数组进行遍历的,因此在遍历的过程中会完成对dp数组的维护。

最终我们需要得知最长递增子序列,也就是dp数组当中的最大值,直接使用 Golang 的库函数slices.Max(dp)即可。

思路 2:二分查找

原题目描述当中提到,请使用O(nlog⁡n)O(n\log{n})O(nlogn)的算法来完成进阶做法,实际上这道题目的最优解是使用二分查找。需要注意的是,我们只能对明确具有单调性质的序列使用二分查找算法,显然原题当中给出的数组并不是一个具有单调性的数组,那么我们如何使用二分查找呢?

实际上,我们需要维护一个额外的数组,它记录的就是当前子序列0...i的最长递增子序列,我们将其命名为tails,并初始化其长度为nn := len(nums))。我们对nums数组进行遍历,期间我们同时维护两个信息,分别是tails数组以及其右边界的尾后下标。针对每个nums[i],我们需要在区间0...res当中进行一次二分查找,来找到放置nums[i]tails数组(当前最长上升子序列)当中的位置。当我们找到这个位置之后,令tails[l] = nums[i],并判断rres的关系:如果r == res,就说明右边界没有移动,新放置到tails当中的元素在res这个位置上,此时需要令res += 1;否则,说明tails[0..<res]的某个位置被修改了,此时不需要移动res

举个简单的例子,针对序列[7, 8, 9, 1, 2, 3],我们肉眼可以观察到最长上升子序列的长度就是3。对应到该算法当中,我们初始化了一个长度为6的序列tails,初始时res的位置是0。接下来我们开始对nums进行遍历,每次遍历时令l, r := 0, res。显然在第一次遍历时,l == r(它们都是0),我们放置tails[l] = nums[i],此时的tails = [7, 0, 0, 0, 0, 0],由于res == r,它们都是0,所以令res += 1

接下来,nums[1] == 8,初始时l, r := 0, 1,通过二分查找,最终的l == 1,因此将nums[1]的元素8放置到tails[1]这个位置。由于此时仍然有r == res,因此res += 1

然后,nums[2] == 9,和方才的情况相同,tails[3] == 9res += 1

nums[3] == 1开始,情况开始不同。此时l, r := 0, 3,通过二分查找,放置nums[3] == 1的位置就是tails[0],此时tails[0..<res]的子序列变为了[1, 8, 9],最长上升子序列的长度仍然是3,只不过序列的值变化了。由于右指针r向左移动了,不满足r == res,因此res不必移动。

以此类推,最终tails[0..<res]的值其实是[1, 2, 3],长度就是res的值。

由此,最终的答案就是res

Golang 题解

思路 1:动态规划

go 复制代码
func lengthOfLIS(nums []int) int {
    n := len(nums)
    dp := make([]int, n)

    for i := 0; i < n; i ++ {
        dp[i] = 1
        for j := 0; j < i; j ++ {
            if nums[j] < nums[i] {
                dp[i] = max(dp[i], dp[j] + 1)
            }
        }
    }

    return slices.Max(dp)
}

思路 2:二分查找

go 复制代码
func lengthOfLIS(nums []int) int {
    n := len(nums)
    tails, res := make([]int, n), 0

    for i := 0; i < n; i ++ {
        l, r := 0, res
        for l < r {
            mid := (l + r) / 2
            if tails[mid] < nums[i] {
                l = mid + 1
            } else {
                r = mid
            }
        }
        tails[l] = nums[i]
        if r == res {
            res += 1
        }
    }

    return res
}
相关推荐
夏鹏今天学习了吗7 小时前
【LeetCode热题100(82/100)】单词拆分
算法·leetcode·职场和发展
mit6.8248 小时前
mysql exe
算法
2501_901147838 小时前
动态规划在整除子集问题中的应用与高性能实现分析
算法·职场和发展·动态规划
中草药z9 小时前
【嵌入模型】概念、应用与两大 AI 开源社区(Hugging Face / 魔塔)
人工智能·算法·机器学习·数据集·向量·嵌入模型
踩坑记录9 小时前
leetcode hot100 189.轮转数组 medium
leetcode
知乎的哥廷根数学学派9 小时前
基于数据驱动的自适应正交小波基优化算法(Python)
开发语言·网络·人工智能·pytorch·python·深度学习·算法
ADI_OP9 小时前
ADAU1452的开发教程10:逻辑算法模块
算法·adi dsp中文资料·adi dsp·adi音频dsp·adi dsp开发教程·sigmadsp的开发详解
xingzhemengyou19 小时前
C语言 查找一个字符在字符串中第i次出现的位置
c语言·算法
Dream it possible!10 小时前
LeetCode 面试经典 150_二分查找_在排序数组中查找元素的第一个和最后一个位置(115_34_C++_中等)
c++·leetcode·面试
小六子成长记11 小时前
【C++】:搜索二叉树的模拟实现
数据结构·c++·算法