Leetcode 300. 最长递增子序列
问题:给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列。
算法1:记忆化搜索
创建一维数组 memo 并赋初始值为 0 ,表示没有被计算过。
进入 dfs 函数。从后往前递归,如果满足前面字母比后面字母小,那么进行下一层递归(向前)更新 memo 。
在函数的最后返回值时,要重新遍历递归的原因是:函数 dfs 传入的参数是子序列末尾数字的位置,末尾数字已经定了,所以要遍历一遍。
代码:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
if(n == 0) return 0;
vector<int> memo(n);
auto dfs = [&](auto &&dfs,int i){
int &res = memo[i];
if(res) return res;// 被计算过
for(int j = 0;j < i;j++)
if(nums[j] < nums[i]) res = max(dfs(dfs,j),res);
return ++res;
};
int ans = 0;
for(int i = 0;i < n;i++) ans = max(ans,dfs(dfs,i));
return ans;
}
};
算法2:1:1翻译成递推
代码:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
if(n == 0) return 0;
vector<int> memo(n);
for(int i = 0;i < n;i++){
memo[i] = 0;
for(int j = 0;j < i;j++)
if(nums[j] < nums[i]) memo[i] = max(memo[i],memo[j]);
memo[i]++;
}
return ranges::max(memo);
}
};
算法3:贪心 + 二分查找
遍历 nums 数组,使用二分查找方法找到在 g 数组中,所遍历到的元素应该存放的位置,利用 lower_bound( ) 函数。如果在 g 数组的中间找到应该存放的位置,就替换掉这个元素(注意是替换不是插入)。替换掉这个元素后,数组 g 的长度保持不变,除非后续遍历能够完全覆盖数组 g 后面的数字,也就是记录了之前最长子序列长度。
lower_bound( ) : 返回指向第一个值不小于 val 的位置,也就是返回第一个大于等于 val 值的位置。(通过二分查找)。
代码:
class Solution {
public:
int lengthOfLIS(vector<int> &nums) {
vector<int> g;
for (int x : nums){// 遍历nums数组
auto it = ranges::lower_bound(g, x);//在g中找到可以插入x的位置。前提是有序的情况下
if (it == g.end()) g.push_back(x); // 如果这个位置在末尾,则添加到g的尾部
else *it = x;//否则替换掉那个位置的元素
}
return g.size();
}
};
算法4:原地修改
在 算法3 的基础上,取消数组 g 的使用,改为原地修改,即在遍历 nums 数组的过程中,如果符合条件,则将其填入对应位置,按照上一方法的思路,如果是在数组最后添加元素,则填完元素后让 end 指针向后移动一位。
代码:
class Solution {
public:
int lengthOfLIS(vector<int> &nums) {
auto end = nums.begin();
for (int x : nums){
auto it = lower_bound(nums.begin(), end, x);
*it = x;
if (it == end) end++;
}
return end - nums.begin();
}
};