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();}
};