问题
最长上升子序列是一类经典的动态规划问题。
给定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;
}