最长递增子序列

leetcode 300题
切入点:
  1. 题目中有“递增”、“序列”这样的字眼,过程中前一个状态和后一个状态是有关系,想到使用动态规划。
  2. 尝试定义状态,并从题目中抽象出前后状态的转移关系: x i = f ( x i − 1 ) x_i = f(x_{i-1}) xi=f(xi1)
动手写出状态转移方程:
  1. 定义状态
    题目要求最长子序列,可以定义子序列长度为状态。
    为满足无后效性,定义状态 d p [ i ] dp[i] dp[i]为以 n u m s [ i ] nums[i] nums[i]为结尾的递增子序列的最大长度。
    最终的输出为 m a x ( d p [ i ] ) , i = 0 , 1.. , n max(dp[i]), i=0,1..,n max(dp[i]),i=0,1..,n

  2. 状态转移
    现在考虑:从 d p [ i − 1 ] dp[i-1] dp[i1]是否能直接得到 d p [ i ] dp[i] dp[i],似乎并不可以。 d p [ i ] dp[i] dp[i]是以 n u m s [ i ] nums[i] nums[i]结尾的最长递增子序列的长度。它的转移过程具体是

    如果 n u m s [ j ] < n u m s [ i ] nums[j] < nums[i] nums[j]<nums[i],则 d p [ i ] = m a x ( d p [ i ] , d p [ j ] + 1 ) dp[i] = max(dp[i], dp[j] + 1) dp[i]=max(dp[i],dp[j]+1), 其中 0 < = j < i 0<= j < i 0<=j<i

完整代码(C++):
int longestIncreasingSubsequence(vector<int>& nums){
	int n = nums.size();
	vector<int> dp(n, 1); 
	for (int i = 0; i < n; ++i){ //计算一nums中每个元素为结尾的最长递增子序列的长度
		for (int j = 0; j < i; ++j){ //nums[i]之前,以nums[j]为结尾的最长递增子序列的长度
			if (nums[i] > num[j]){ //满足这个条件,nums[i]可以拼接到以nums[j]为结尾的序列的后边,形成以nums[i]为结尾的递增序列。
				dp[i] = max(dp[i], dp[j] + 1); //取最长的长度。
			}
		}
	}
	int max_len = *max_element(dp.begin(), dp.end()); //返回最长的长度。
	return max_len;
}

时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n ) O(n) O(n)

方法二:贪心算法

思想:推演过程中,使递增子序列结尾的元素尽量小(贪心),那么最终的长度才可能尽量长

定义 d p [ l e n ] = n u m s [ i ] dp[len]=nums[i] dp[len]=nums[i],长度为 l e n len len的递增子序列,最小的结尾

完整代码(c++)

int longestIncreasingSubsequence(vector<int>& nums){
	int n = nums.size();
	vector<int> dp(n+1,0);
	int len = 1;
	dp[len] = nums[0]; //长度为1的递增子序列,末尾元素置为nums[0],后续会更新。
	for(int i = 1; i < n; ++i){
		//第i个元素大于长度为len的递增子序列的末尾元素,则可以拼接到后边,是最长递增子序列的长度+1.
		if(dp[len] < nums[i]){ 
			dp[++len] = nums[i];
		}else{ //否则,更新前面长度为j的最长子序列的末尾元素。
			int pos = 0;
			for(int j = 0; j < len; ++j){
				if(dp[n] < nums[i]){
					pos = j;
				}
			}
			dp[pos+1]=nums[i];
		}
	}
	return len;
}

时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n ) O(n) O(n)

方法2优化,二分查找

方法2中,更新当 d p [ l e n ] > = n u m s [ i ] dp[len] >= nums[i] dp[len]>=nums[i]时,需要从 d p [ 1 , . . . , l e n − 1 ] dp[1,...,len-1] dp[1,...,len1]中找到一个 p o s pos pos,使得 d p [ p o s ] < n u m s [ i ] < d p [ p o s + 1 ] dp[pos] < nums[i] < dp[pos+1] dp[pos]<nums[i]<dp[pos+1],并更新 d p [ p o s + 1 ] = n u m s [ i ] dp[pos+1]=nums[i] dp[pos+1]=nums[i]
由于 d p [ 1 , . . . , l e n ] dp[1,...,len] dp[1,...,len]是单调递增的,可以使用二分法来优化时间复杂度。

完整代码(C++)

int longestIncreasingSubsequence(vector<int>& nums){
	int n = nums.size();
	vector<int> dp(n,0); 
	int len = 0;
	dp[len] = nums[0];
	
	for(int i=0; i<n; ++i)
	{
		if(dp[len] < nums[i]){
			dp[++len] = nums[i];
		}else{
			int pos = 0;
			int l=1, r = len;
			while(l <= r){
				int mid = (l+r) >> 1;
				if(dp[mid] < nums[i]){
					pos = mid;
					l = mid+1;
				}else{
					r = mid-1;
				}
			}
			dp[pos+1] = nums[i];
		}
	}
	return len;
}

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度 O ( n ) O(n) O(n)

你可能感兴趣的:(算法,动态规划,最长递增子序列,算法)