【LeetCode】P300 最长上升子序列(未完结)

P300 最长上升子序列

题目链接:300. 最长上升子序列.

题目描述

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

示例:

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

说明:

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

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

题解

方法一:动态规划

链接:动态规划.

思路

  • ①将原问题分解为子问题

“求序列的前 i i i 个元素的最长上升子序列的长度”是个子问题,但这样分解子问题,不具有“无后效性”的特点。

定义 d p [ i ] = x dp[i]=x dp[i]=x 为前 i i i 个元素( n u m s [ 0 ] 、 n u m s [ 1 ] … n u m s [ i − 1 ] nums[0]、nums[1] … nums[i-1] nums[0]nums[1]nums[i1])的最长上升子序列的长度为 x x x,但在这前 i i i 个元素中,可能有多个上升子序列同时满足 d p [ i ] = x dp[i]=x dp[i]=x。有的子序列的最后一个元素比 n u m s [ i ] nums[i] nums[i] 小,则加上 n u m s [ i ] nums[i] nums[i] 就能形成更长的上升子序列;有的序列最后一个元素不比 n u m s [ i ] nums[i] nums[i] 小,不能加上 n u m s [ i ] nums[i] nums[i] 形成更长的上升子序列。所以在当前的若干个状态值确定后,此后的过程演变不仅与这若干个状态的值有关,而且与之前经过哪条路径演变到当前这个状态有关,在本题中即: d p [ i + 1 ] dp[i+1] dp[i+1] 不仅与 d p [ i ] dp[i] dp[i] 有关,而且与在前 i i i 个元素中选择哪条最长上升子序列的最后一个元素与 n u m s [ i ] nums[i] nums[i] 进行比较有关。

子问题:“求以元素 n u m s [ i ] nums[i] nums[i] i = 0 、 1 、 2... n − 1 i=0、1、2 ... n-1 i=012...n1 n n n 为数组 n u m s nums nums 中元素个数)为终点的最长上升子序列的长度”。定义 d p [ i ] dp[i] dp[i] 为以元素 n u m s [ i ] nums[i] nums[i]为终点的最长上升子序列的长度,共有 n n n 个子问题,将这 n n n 个子问题都解决了,那最大解就是原问题的解了,这就满足了问题具有最优子结构性质

  • ②确定状态

子问题只与一个变量有关:元素下标 i i i,所以每个子问题中最长上升子序列的终点元素的下标 i i i,就是状态,而状态 i i i 对应的值就是以元素 n u m s [ i ] nums[i] nums[i] 为终点的最长上升子序列的长度,所以我们只用一个一维数组就可以存储各个状态的值。共有 n n n 个状态,它们构成状态空间。

  • ③确定一些边界状态(初始状态)的值

显然,以元素 n u m s [ 0 ] nums[0] nums[0] 为终点的最长上升子序列的长度为 1 1 1,所以有 d p [ 0 ] = 1 dp[0]=1 dp[0]=1

  • ④确定状态转移方程

在计算 d p [ i ] dp[i] dp[i] 时,我们假设已经将 d p [ 0 ] 、 d p [ 1 ] 、 d p [ i − 1 ] dp[0]、dp[1]、dp[i-1] dp[0]dp[1]dp[i1] 都已经计算出来了,那么要计算 d p [ i ] dp[i] dp[i] 的值就要先在 d p [ 0 ] 、 d p [ 1 ] . . . d p [ i − 1 ] dp[0]、dp[1] ... dp[i-1] dp[0]dp[1]...dp[i1] 中寻找 d p [ j ] dp[j] dp[j] 0 ⩽ j ⩽ i − 1 , j ∈ Z 0\leqslant j\leqslant i-1,j\in \mathbb{Z} 0ji1jZ), d p [ j ] dp[j] dp[j] 有:

d p [ j ] = max ⁡ ( d p [ k ] , k ∈ s e t ) , s e t = { k ∣ n u m s [ k ] < n u m s [ i ] ( 0 ⩽ k ⩽ i − 1 , k ∈ Z ) } dp[j]=\max(dp[k],k\in set),set=\{k|nums[k]<nums[i](0\leqslant k\leqslant i-1,k\in \mathbb{Z})\} dp[j]=max(dp[k]kset)set={knums[k]<nums[i](0ki1kZ)}

找到 d p [ j ] dp[j] dp[j] 后,则有 d p [ i ] = d p [ j ] + 1 dp[i]=dp[j]+1 dp[i]=dp[j]+1

也就是 d p [ i ] dp[i] dp[i] 的值为满足终点元素在 n u m s [ i ] nums[i] nums[i] 的左边、终点元素小于 n u m s [ i ] nums[i] nums[i] 的上升子序列中的最长上升子序列的长度加 1。

我们可以得到状态转移方程:
d p [ i ] = { max ⁡ ( d p [ k ] , k ∈ s e t ) + 1 s e t ≠ ∅ 1 s e t = ∅ dp[i] = \begin{cases} \max(dp[k],k\in set)+1 &set\neq \varnothing \\ 1 &set=\varnothing \end{cases} dp[i]={max(dp[k]kset)+11set=set=

s e t = { k ∣ n u m s [ k ] < n u m s [ i ] ( 0 ⩽ k ⩽ i − 1 , k ∈ Z ) } set=\{k|nums[k]<nums[i](0\leqslant k\leqslant i-1,k\in \mathbb{Z})\} set={knums[k]<nums[i](0ki1kZ)}

状态转移方程是递推形式的,所以在计算 d p [ i ] dp[i] dp[i] 时,我们已经将 d p [ 0 ] 、 d p [ 1 ] 、 d p [ i − 1 ] dp[0]、dp[1]、dp[i-1] dp[0]dp[1]dp[i1] 都已经计算出来了。

  • 原问题的解

最后,整个数组的最长上升子序列的长度 m a x L e n maxLen maxLen 即数组 d p dp dp 中的最大值。
m a x L e n = max ⁡ ( d p [ i ] ) , ( 0 ⩽ i ⩽ n − 1 , i ∈ Z ) maxLen=\max(dp[i]),(0\leqslant i\leqslant n-1,i\in \mathbb{Z}) maxLen=max(dp[i])(0in1iZ)

算法

class Solution {
public:
	int lengthOfLIS(vector<int>& nums) {
		if(nums.size()==0){
			return 0;
		}
		int size=nums.size();
		vector<int> dp(size,1);
		int maxLen=dp[0];
		for(int i=1;i<size;++i){
			int temp=1;
			for(int j=0;j<i;++j){
				if(nums[i]>nums[j]){
					temp=dp[j]+1>temp?dp[j]+1:temp;
				}
			}
			dp[i]=temp;
			maxLen=dp[i]>maxLen?dp[i]:maxLen;
		}
		return maxLen;
	}
};

复杂度分析

假设数组中元素的个数为 n n n

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2),因为动态规划中状态的数目为 n n n,在计算状态 i i i 的值 d p [ i ] dp[i] dp[i] 时需要遍历 d p [ 0 ] 、 d p [ 1 ] . . . d p [ i − 1 ] dp[0]、dp[1]...dp[i-1] dp[0]dp[1]...dp[i1],时间复杂度为 O ( n ) O(n) O(n),由动态规划解题的时间复杂度计算公式:
    时 间 复 杂 度 = 状 态 的 数 目 ⋅ 计 算 每 个 状 态 所 需 时 间 时间复杂度=状态的数目⋅计算每个状态所需时间 =所以得到时间复杂度 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n ) O(n) O(n),需要一个长度为 n n n 一维数组 d p dp dp 来存储各个状态的值。

方法二:贪心 + 二分查找

思路

算法

复杂度分析

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值