给你一个整数数组 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 。
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
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;
}
};
通过重新设计状态定义,使整个 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] 存储序列长度为 i+1 的最小末尾数。这样,dp 未必是真实的最长上升子序列,但其长度就是最终的子序列长度。 比如 {1, 2, 9, 10, 3, 4, 5} 的替换过程。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
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();
}
};