300. 最长递增子序列 ●●
描述
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
题解
1. 动态规划
- dp[i] 表示以第 i 个数字nums[i]为结尾的最长递增序列长度;
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
位置 i 的最长升序子序列等于j从0到 i-1各个位置的最长升序子序列 + 1 的最大值。
注意这里不是要dp[i] 与 dp[j] + 1进行比较,而是我们要取dp[j] + 1的最大值。- 每一个 i,对应的dp[i](即最长上升子序列)起始大小至少都是1;
- 双层循环,从前往后遍历。
- 时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n 为数组 nums 的长度。动态规划的状态数为 n,计算状态 dp[i] 时,需要 O(n) 的时间遍历 dp[0…i−1] 的所有状态。
- 空间复杂度:O(n),需要额外使用长度为 n 的 dp 数组。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
int ans = 1;
vector<int> dp(n, 1); // dp[i] 表示以第i个数字为结尾的最长递增序列长度
for(int i = 1; i < n; ++i){ // nums[i]结尾
for(int j = 0; j < i; ++j){
if(nums[i] > nums[j]){
dp[i] = max(dp[i], dp[j] + 1); // 升序时,0到i-1各个位置的最长升序子序列 + 1
}
}
ans = max(ans, dp[i]); // 取最大值
}
return ans;
}
};
2. DP + 二分查找
通过重新设计状态定义,使整个 dp 为一个排序列表;这样在计算每个 dp[k] 时,就可以通过二分法遍历 [0,k) 区间元素,将此部分复杂度由 O(N) 降至 O(logN)。
定义 dp[i] 表示最长递增序列长度为 i+1 的最小末尾数,如 {1, 2, 4, 3} 中,最长递增序列长度为 3 的最小末尾数 dp[2] = 3;
此时 dp 数组一定是严格递增的,即 k < n 时有dp[ k ] < dp[ n ],当尽可能使每个子序列尾部元素值最小的前提下,子序列越长,其序列尾部元素值一定更大。(可用反证法证明)。
步骤为:
- 新建dp数组,用于存放最长递增序列长度为 i+1 的最小末尾数,初始化dp[0] = nums[0];
- 遍历数组nums,对每个元素进行比较插入操作,
1)如果dp中元素都小于它,则尾部插入新元素;
2)否则,用二分法找到第一个大于等于 nums[i] 的位置,进行替换。
总之,思想就是让 dp[i] 存储序列长度为 i+1 的最小末尾数。这样,dp 未必是真实的最长上升子序列,但其长度就是最终的子序列长度。 比如 {1, 2, 9, 10, 3, 4, 5} 的替换过程。
- 时间复杂度 O ( N l o g N ) O(NlogN) O(NlogN) : 遍历 nums 列表需 O(N),在每个 nums[i] 二分法需 O(logN)。
- 空间复杂度 O(N) : tails 列表占用线性大小额外空间。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> dp; // dp[i] 表示最长递增序列长度为i+1的最小末尾数
dp.emplace_back(nums[0]); // dp初始化
for(int i = 1; i < n; ++i){
int l = 0;
int r = dp.size()-1;
if(dp[r] < nums[i]){ // dp数组最大数小于nums[i],尾部插入元素
dp.emplace_back(nums[i]);
continue;
} // nums[i]在dp数组范围内
while(l <= r){ // 二分查找第一个大于等于nums[i]的位置,进行替换
int m = l + (r-l) / 2;
if(dp[m] >= nums[i]){ // 若非要求严格递增,将此行>=改为>,行10的<改为<=
r = m - 1;
}else{
l = m + 1;
}
}
dp[l] = nums[i]; // 元素替换
}
return dp.size();
}
};