【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
}
相关推荐
Forever Nore1 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考2 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX3 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面4 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号4 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽4 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI4 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者6 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者6 小时前
【征程6】校准量化中HistogramObserver解析
算法
OuO-27 小时前
笔试强训 Day 34:ISBN 号码、kotori 和迷宫、矩阵最长递增路径
java·算法·矩阵