题目描述:
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
思路分析:
思路一:根据题目的提示,利用动态规划,可以用O(N^2)的复杂度解这题。直接利用一个dp数组,用从后往前的方式存每个元素当前的最长上升序列,更新的状态转移方程就是dp[i] = max(dp[i], dp[j]+1),这里j的取值是从i+1到dp.size()。
思路二:由于题目的进阶要求是需要将复杂度降到O(NlogN),logn顺利成章会想到用二分查找来降低这个复杂度。实际上这部分的思想是维护一个tail数组,遍历nums数组时,每次都在tail数组中去找大于当前值的树,若有则替换,若没有则将当前值加入tail数组。进行替换的原因是在后续的查找中,可以找到更长的子序列,由于当前的tail中的元素更小了。而实际上,只有在添加新元素时会改变最长子序列的大小,因此这个tail数组的长度始终维持在当前最长子序列的长度。这里在查找数用到了二分查找,代码中直接调用了lower_bound()函数。关于这个函数的说明如下:
同时注意区分另一个upper_bound函数,这个返回值是第一个大于val值的地址。第一个first参数是一段连续空间的首地址,last是连续空间末端的地址,val是要查找的值。调用lower_bound()的前提是这段连续的空间里的元素是有序(递增)的。
然后lower_bound()的返回值是第一个大于等于val的值的地址,用这个地址减去first,得到的就是第一个大于等于val的值的下标
代码:
思路一:
1 class Solution { 2 public: 3 int lengthOfLIS(vector<int>& nums) { 4 if(nums.size()==0) 5 return 0; 6 vector<int>dp(nums.size(), 1); 7 for(int i=nums.size()-1; i>=0; i--) 8 { 9 for(int j=i+1; j<nums.size(); j++) 10 { 11 if(nums[j] > nums[i]) 12 { 13 dp[i] = max(dp[i], dp[j]+1); 14 } 15 } 16 } 17 int max = 0; 18 for(int i=0; i<dp.size(); i++) 19 { 20 if(dp[i]>max) 21 max = dp[i]; 22 } 23 return max; 24 } 25 };
思路二:
1 class Solution { 2 public: 3 int lengthOfLIS(vector<int>& nums) { 4 if(nums.size()==0) 5 return 0; 6 vector<int> res; 7 for(int i=0; i<nums.size(); i++) 8 { 9 auto iter = lower_bound(res.begin(), res.end(), nums[i]); 10 if(iter == res.end()) 11 res.push_back(nums[i]); 12 else 13 *iter = nums[i]; 14 } 15 return res.size(); 16 } 17 };