【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
}
相关推荐
隐语SecretFlow2 小时前
隐语SML0.1.0版本发布!SPU开源机器学习Python算法库
python·算法·机器学习
zdd567892 小时前
GIN索引原理
运维·算法·postgresql
byzh_rc2 小时前
[算法设计与分析-从入门到入土] 查找&合并&排序&复杂度&平摊分析
数据结构·数据库·人工智能·算法·机器学习·支持向量机·排序算法
谎言西西里8 小时前
LeetCode 热题100 --- 双指针专区
算法
leo__52011 小时前
基于两步成像算法的聚束模式SAR MATLAB实现
开发语言·算法·matlab
前端小白在前进11 小时前
力扣刷题:在排序数组中查找元素的第一个和最后一个位置
数据结构·算法·leetcode
某林21213 小时前
基于SLAM Toolbox的移动机器人激光建图算法原理与工程实现
stm32·嵌入式硬件·算法·slam
修炼地13 小时前
代码随想录算法训练营第四十三天 | 图论理论基础、深搜理论基础、卡码网98. 所有可达路径、797. 所有可能的路径、广搜理论基础
算法·深度优先·图论
iAkuya13 小时前
(leetcode)力扣100 23反转链表(迭代||递归)
算法·leetcode·链表