【leetcode】300.最长上升子序列(动态规划多种解法, java实现)

300. 最长上升子序列

难度中等

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:

  • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
  • 你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

分析

image.png

首先,需要对「子序列」和「子串」这两个概念进行区分;

  • 子序列(subsequence)

子序列并不要求连续,例如:序列 [4, 6, 5][1, 2, 4, 3, 7, 6, 5] 的一个子序列。

  • 子串(substring、subarray)

子串一定是连续的,例如:「力扣」第 3 题:“无重复字符的最长子串”,「力扣」第 53 题:“最大子序和”

其次,题目中的「上升」的意思是「严格上升」,[1, 2, 2, 3] 都不能算作「上升子序列」;

第三,子序列中元素的相对顺序很重要。

它们必须保持在原始数组中的相对顺序。如果把这个限制去掉,将原始数组去重以后,元素的个数即为所求。


方法一:暴力解法

  • 使用「回溯搜索算法」或者「位运算」的技巧,可以得到输入数组的所有子序列,时间复杂度为 O ( 2 N ) O(2^N) O(2N)
  • 再对这些子串再依次判定是否为「严格上升」,时间复杂度 为O(N),所以总的时间复杂度为: O ( N 2 N ) O(N2^N) O(N2N)

这道题是很经典的使用「动态规划」算法解决的问题。

「动态规划」的基本思想是:

从一个小问题出发(「动态」这个词的意思的意思),通过「状态转移」,并且逐步记录求解问题的过程(「规划」这个词的意思,就是「打表格」,以「以空间换时间」),逐步得到所求规模的问题的解。

对于这道题来说,就是一个数一个数地去考虑(区别于递归的写法,直接面对问题求解)。


方法二:动态规划

  • 首先考虑题目问什么,就把什么定义成状态。
  • 题目问最长上升子序列的长度,其实可以把「子序列的长度」定义成状态,但是发现「状态转移」不好做;
  • 把「子序列的长度」定义成状态,事实上也可以,只是目前这样定义状态,没有定义得很清晰,具体做法在下文「方法三」;
  • 为了从一个较短的上升子序列得到一个较长的上升子序列,我们主要关心这个较短的上升子序列结尾的元素。由于要保证子序列的相对顺序,在程序读到一个新的数的时候,如果比已经得到的子序列的最后一个数还大,那么就可以放在这个子序列的最后,形成一个更长的子序列;

于是我们可以这样定义状态:

第 1 步:定义状态

由于一个子序列一定会以一个数结尾,于是将状态定义成:dp[i] 表示nums[i] 结尾的「上升子序列」的长度。注意:这个定义中 nums[i] 必须被选取,且必须是这个子序列的最后一个元素

第 2 步:考虑状态转移方程
  • 遍历到 nums[i] 时,需要把下标 i 之前的所有的数都看一遍;
  • 只要 nums[i] 严格大于在它位置之前的某个数,那么 nums[i] 就可以接在这个数后面形成一个更长的上升子序列;
  • 因此,dp[i] 就等于下标 i 之前严格小于 nums[i] 的状态值的最大者 +1。

语言描述:在下标 i 之前严格小于 nums[i] 的所有状态值中的最大者 + 1。

符号描述:

d p [ i ] = max ⁡ 0 ≤ j < i , n u m s [ j ] < n u m s [ i ] d p [ j ] + 1 , dp[i] = \max_{0 \le j < i, nums[j] < nums[i]} {dp[j] + 1}, dp[i]=max0j<i,nums[j]<nums[i]dp[j]+1,

第 3 步:考虑初始化
  • dp[i] = 1,1 个字符显然是长度为 1 的上升子序列。
第 4 步:考虑输出
  • 这里要注意,不能返回最后一个状态值;
  • 还是根据定义,最后一个状态值只是以 nums[len - 1] 结尾的「上升子序列」的长度;
  • 状态数组 dp 的最大值才是最后要输出的值。

max

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值