LeetCode300 最长递增子序列

52 阅读3分钟

leetcode.cn/problems/lo…

image.png

解法一:自底向上的递推DP

我们设计动态规划算法,不是需要一个 dp 数组吗?我们可以假设 dp[0...i-1] 都已经被算出来了,然后问自己:怎么通过这些结果算出 dp[i]?

定义dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度。

根据这个定义,我们就可以推出 base case:dp[i] 初始值为 1,因为以 nums[i] 结尾的最长递增子序列起码要包含它自己。最终答案(递增子序列的最大长度)应该是整个 dp 数组中的最大值。

接下来如何确定状态转移方程?

例如,现在想求 dp[5] 的值,也就是想求以 nums[5] 为结尾的最长递增子序列。

image.png

nums[5] = 3,既然是递增子序列,我们只要找到前面那些结尾比 3小的递增子序列,然后把 3接到这些子序列末尾,就可以形成一个新的递增子序列,而且这个新的子序列长度+1。

而题目要求最长的递增子序列,那么,nums[0] 和 nums[4] 都是小于 nums[5] 的,然后对比 dp[0] 和 dp[4] 的值,我们让 nums[5] 和更长的递增子序列结合。

func lengthOfLIS(nums []int) int {
    // dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度
    dp := make([]int, len(nums))
    for i:= 0; i<len(dp); i++{ // 初始化都为1,因为至少包含当前位置上的元素
        dp[i] = 1
    }
    for i:= 0; i<len(dp); i++{
        for j:=0; j<i; j++{ // 寻找 nums[0..i-1] 中比 nums[i] 小的元素
            if nums[j] < nums[i]{
                // 把 nums[i] 接在后面,即可形成长度为 dp[j] + 1的递增子序列
                dp[i] = max(dp[i], dp[j]+1)
            }
        }
    }
    res := 0
    for _, v := range dp{
        res = max(res, v)
    }
    return res
}

func max(a, b int) int{
    if a > b{
        return a
    }
    return b
}

时间复杂度:O(N^2),遍历计算 dp 列表需 O(N),计算每个 dp[i] 需 O(N),显然不是最优解

空间复杂度 O(N): dp 列表占用线性大小额外空间

优化版:二分查找

参考 labuladong.online/algo/dynami…

func lengthOfLIS(nums []int) int {
    // 初始所有牌是平铺的,没有牌盖在上面
    // top[i]表示长度为i+1的子序列尾部元素的值,如果有多个子序列长度一样,这里存的是最小的那个序列尾部元素
    top := make([]int, len(nums)) 
    // 牌堆数初始化为 0
    var piles int
    for i := 0; i < len(nums); i++ {
        // 要处理的扑克牌
        poker := nums[i]
        // ***** 在所有堆上搜索左侧边界的二分查找 *****
        var left, right int = 0, piles
        for left < right {
            mid := (left + right) / 2
            if top[mid] > poker {
                right = mid
            } else if top[mid] < poker {
                left = mid + 1
            } else if top[mid] == poker{
                right = mid
            }
        }
        // ********************************
        // 没找到合适的牌堆,新建一堆
        if left == piles {
            piles++
        }
        // 把这张牌放到牌堆顶
        top[left] = poker
    }
    fmt.Println(top)
    // 牌堆数就是 LIS 长度
    return piles
}

时间复杂度:O(N*Log(N))

空间复杂度 O(N)