300. 最长递增子序列(中等)
方法一:动态规划
-
思路
- 通常来说,子序列不要求连续,而子数组或子字符串必须连续;
- 对于子序列问题,第一种动态规划方法是,定义 dp 数组,其中 dp[i] 表示以 i 结尾的子序列的性质。在处理好每个位置后,统计一遍各个位置的结果即可得到题目要求的结果。
- 在本题中, dp[i] 可以表示为以 i 结尾的、最长子序列长度。对于每个位置 i ,如果其之前的某个位置 j 所对应的数字小于位置 i 所对应的数字 ,则我们可以获得一个以 i 结尾、长度为 dp[j] + 1 的子序列。为了遍历所有情况,我们需要对 i 和 j 进行两层循环,其时间复杂度为 O(n2)。
-
代码
class Solution { public: int lengthOfLIS(vector<int>& nums) { int ans = 0; vector<int> dp(nums.size(), 1); for(int i=0; i<nums.size(); ++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; } };
-
收获
- 我一开始的想法是将 dp[i] 表示为前 i 位最长递增子序列的长度,但是发现并不好算。
方法二:二分查找
-
思路
- 本题还可以使用二分查找将时间复杂度降为 O(n logn)。
- 定义 dp数组,其中 dp[k] 表示存储长度为 k+1 的最长递增子序列的最后一个数字。
- 遍历每一个位置 i ,如果对应的数字大于 dp 数组中所有数字的值,那么就把他放在 dp 数组的尾部,表示最长递增子序列长度+1;如果这个数字介于当前dp数组最小值和最大值之间,那么将最大值更新为当前数组,使得之后构成递增序列的可能性加大。
- 根据这个方法维护 dp 数组永远是递增的,因此可以用二分查找加速搜索。
- 然而,使用这个方法需要注意, dp数组的最终形式不一定是合法的排列形式,但最优解一定出现在遍历的过程中。
-
代码
class Solution { public: int lengthOfLIS(vector<int>& nums) { int ans = 0; vector<int> dp; dp.push_back(nums[0]); for(int i=1; i<nums.size(); ++i){ if(dp.back() < nums[i]){ dp.push_back(nums[i]); } else{ // 通过二分查找,返回第一次出现大于等于 nums[i] 的地址 auto itr = lower_bound(dp.begin(), dp.end(), nums[i]); *itr = nums[i]; } } return dp.size(); } };
-
收获
- 这个思路还挺新奇的,学习了 lower_bound 函数的用法 lower_bound。