动态规划——最长上升子序列
问题
最长上升子序列是一类经典的动态规划问题。
给定N个数字, A1,A2,....An,从中选择k个数字 At1, At2,... Atk,满足 1 =< t1 < t2 < .. < tk <= n,且 At1 < At2 < ... < Atk,求满足要求的最大的k。
分析
设一个动归数组dp,dp[i]表示以第i个数字(即Ai)结尾的最长上升子序列的长度,显然这种问题的划分满足无后效性和最优子结构。同时,可以很方便的推出递推关系
dp[i] = max{1, dp[j] +1} (j < i 且 Ai > Aj)
实现
memset(dp, 0, sizeof(dp)); for(int i = 1; i <= n; i ++){ //数组从1开始 int max = 0; for(int j = 1; j < i; j ++){ if(A[i] > A[j]) dp[i] = max(dp[i], dp[j] + 1); if (dp[i] > max) max = dp[j]; } }
复杂度分析
显然,时间复杂度为O(n^2), 空间复杂度为O(n).
O(nlogn)复杂度解法
其实还存在一种时间复杂度为O(n*logn)的算法,也是使用动态规划思想,状态 dp[p] 为 所有最长长度为p+1的上升子序列中最后一个元素的最小值。
这样,从头到尾遍历数组nums,对于当前的nums[i],在dp中查找第一个比nums[i]大的数 dp[k],如果没找到,则dp数组的长度p增加1,并且设置dp[p] = nums[i],
如果找到,则将 dp[k] 赋值为 nums[i](因为nums[i]是此时最长长度为k+1的上升子序列中最后一个元素的最小值)。
最后dp数组的长度就是最长上升子序列的长度。
int lengthOfLIS(vector<int>& nums) { int n = nums.size(); if(n <= 0) return 0; vector<int> dp(n+1, 0); int len = 0; dp[len] = nums[0]; for(int i = 1; i < n; i ++){ vector<int>::iterator it = lower_bound(dp.begin(), dp.begin() + len + 1, nums[i]); if(it == dp.begin() + len + 1){ dp[++len] = nums[i]; }else { *it = nums[i]; } } return len + 1; }