leetcode-300最长上升子序列

题目描述

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

一开始我的错误想法

想暴力搜索解决,基本思路为有一个数组dp[i]保存以i开始的最长上升子序列长度。两层for循环,外层遍历数组,内层找到以i开始i之后的最大长度,如果后面的元素更大长度就加1.结果发现对于2534着种情况,只统计25,之后34都比5小就不放了。真正长度应为234把5跳过,所以我暴力搜索的想法的问题在于没有对跳过元素的考虑。要想全不情况考虑的话应该是N个数任意取n个数、n-1个数、……1个数的所有情况都考虑,但是这样时间复杂度过大.n-1 + nn-1 + nn-1*n-2+……
错误代码如下:

  1. int lengthOfLIS(vector<int>& nums){
  2. int len = nums.size();
  3. if( len <=1){
  4. return len;
  5. }
  6. vector<int> dp(len,1);
  7. int max_len =1;
  8. for(int i =0; i < len; i++){
  9. int current = i;//以i开始现在最大的数
  10. for(int j = i +1; j < len; j++){
  11. if(nums[j]> nums[current]){
  12. current = j;
  13. dp[i]++;
  14. }
  15. }
  16. max_len = max_len>dp[i]?max_len:dp[i];
  17. }
  18. // 获取了所有长度
  19. return max_len;
  20. }

反思:对于暴力搜索一定要把所有的情况都考虑到,不要想当然,要针对自己的思路设计好测试用例,看是否使用所有情况,如果复杂度过高,则要使用高级的算法去解决。

动态规划 时间复杂度n方 空间复杂度n

思路:状态dp[i]是以第i个元素结尾的最长子序列长度,状态转移方程为dp[i] = max(dp[j])+1;j满足num[i] > nums[j]且j<i的所有dp[j]中最大的dp[j];
原理:10 9 2 5 3 7; 找数值3结尾的最长子序列长度就是遍历3之前小于3的数值且长度最大的dp+1,这里是数值2
代码如下

  1. classSolution{
  2. //最长上升子序列 可以非连续
  3. // 暴力搜索 需要一个数组保存 第i个元素开始的上升子序列长度
  4. public:
  5. int lengthOfLIS(vector<int>& nums){
  6. int len = nums.size();
  7. if( len <=1){
  8. return len;
  9. }
  10. vector<int> dp(len,0);
  11. dp[0]=1;
  12. int max_len =1;
  13. for(int i =1; i < len; i++){
  14. dp[i]=1;//防止i前面没有满足nums[j]<nums[i]的情况 dp[i]为0
  15. for(int j = i -1; j >=0; j--){
  16. if(nums[i]> nums[j]){
  17. dp[i]= dp[i]> dp[j]+1? dp[i]: dp[j]+1;
  18. }
  19. }
  20. max_len = max_len>dp[i]?max_len:dp[i];
  21. }
  22. // 获取了所有长度
  23. return max_len;
  24. }
  25. };

2.2贪心算法+二分查找 时间nlogn 空间n

2.1时间复杂度为n方,其关键在于第二层遍历要找到最大的dp要遍历i-1个元素
算法思想:维持一个当前最长子序列,我们从前向后遍历肯定希望后面的元素看是否可以填到当前最长子序列中。每遍历一个元素,如果比当前最长子序列最后一个元素大,最长子序列增长。如果比当前最长子序列小,(重点内容)我们希望当前最长子序列的最后一个数尽可能的小,这样可以放入更多的数。所以把这个无法放在末尾的数使用替换的方式放进最长子序列使其在上限减小。这样改变长度又缩短了大小。
2534来说
一开始,当前序列2
然后,5>2,当前序列2,5
然后,3<5,放不进去但我们希望上限更小2,3。在个数不变情况下3换5这样上限变小 然后,4>3,当前序列2,3,4
个人感觉是利用了放不进去的情况,用替换的方法构造最优子结构,以简化之后的遍历操作.
以4 10 4 来说
一开始,序列4
然后, 4 10
然后,第二个4<10,放不进去。我们替换变成4 10 注不是4 4 因为4 4 不是严格上升序列
综上在放不进去的情况下就是找序列中第一个大于等于nums【i】的元素dp[j]替换调

classSolution{
//最长上升子序列 可以非连续
// 暴力搜索 需要一个数组保存 第i个元素开始的上升子序列长度
public:
int lengthOfLIS(vector<int>& nums){
int len = nums.size();
if( len <=1){
return len;
}
vector<int> dp;
dp.push_back(nums[0]);
for(int i =1; i < len; i++){
if(nums[i]>*(dp.end()-1)){
dp.push_back(nums[i]);;
}else{
// 需要找到替换哪里,从前往后dp中大于等于nums[i]的第一个数,因为有序可以用二分查找
int low =0, high = dp.size()-1;
int pos =0;
while(high>=low){
int mid =(low + high)/2;
if(nums[i]> dp[mid]){
low = mid+1;
}else{
high = mid-1;
pos = mid;
}
}
dp[pos]= nums[i];
}
// 获取了所有长度
return dp.size();
}
};

截图:

 总结:动态规划将问题由大化小,贪心设计最优子结构给予二分查找的使用条件。

原文地址:https://www.cnblogs.com/linxuesong/p/12773528.html