LeetCode 300:最长递增子序列
题目描述
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例1
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例2
输入:nums = [0,1,0,3,2,3]
输出:4
示例3
输入:nums = [7,7,7,7,7,7,7]
输出:1
数据范围
1 <= nums.length <= 2500
-10^4 <= nums[i] <= 10^4
方法1
题目想要求一个数组的最长递增子序列的长度,我们可以采用动态规划的思想,假设数组长度为len
,维护一个len
长的数组ans
,ans[i]
表示前i+1
个元素(元素下标从0
开始)的最长子序列长度,要求出ans[i]
,就需要让第i
个元素值与第j
个元素值依次进行比较(j >= 0 && j <= i-1
),若第i
个元素值较大则ans[i] = max(ans[i], ans[j] + 1)
。最终求ans
数组中的最大值即可。
算法复杂度:O(n * n)
int lengthOfLIS(vector<int>& nums) {
int len = nums.size(), ans = 0;
vector<int> dp(len, 1);
for(int i = 0; i < len; i++) {
for(int j = 0; j < i; j++) {
if(nums[i] > nums[j])
dp[i] = max(dp[i], dp[j] + 1);
}
ans = max(ans, dp[i]);
}
return ans;
}
方法2
假设nums = [1, 4, 2, 5, 3]
,我们肉眼就能看出最长子序列长度为3,有两个符合的子序列,分别是[1, 2, 3]
,[1, 4, 5]
。假如在数组后再加一个元素4呢?数组nums = [1, 4, 2, 5, 3, 4]
,也很明显答案是4,我们在上面的第一个子序列[1, 2, 3]
的基础上,追加一个元素4
,就是我们的最长子序列。
从上面的例子可以看出,我们每次计算ans[i]
时(ans[i]
表示前i+1
项的最长子序列长度,元素下标从0开始),希望前面子序列最长的情况下各项元素尽可能小,也就是用贪心的思想。
因此我们可以维护一个动态数组d
,用于存放元素值最小的“最长子序列”。也就是当nums = [1, 4, 2, 5, 3]
时,d = [1, 2, 3]
。
那我们应该如何遍历nums
的同时来维护这个动态数组d
呢?
每次将nums[i]
与d
的最后一个元素比大小,若nums[i]
大的话,直接在d
中追加一个元素nums[i]
,并且ans[i]
也就等于d
的长度;若nums[i]
小的话,则在d
中用二分查找找到第一个比nums[i]
大的元素,将其覆盖,ans[i]
就是nums[i]
在d
中的下标加一(因为下标从0
开始)。
到这里理解了基本思路,可以进一步进行空间上的优化,题目所求的是最长子序列长度,不需要设置ans
数组来记录前i
个元素的最长子序列长度,因为数组d
的长度就是最长子序列的长度。
算法复杂度:O(nlgn)
int lengthOfLIS(vector<int>& nums) {
vector<int> d;
int len = nums.size();
for(int i = 0; i < len; i++) {
if(d.empty() || nums[i] > d.back()) {
d.push_back(nums[i]);
}
else {
int t = lower_bound(d.begin(), d.end(), nums[i]) - d.begin();
d[t] = nums[i];
}
}
return d.size();
}