题目:给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入:[10,9,2,5,3,7,101,18]
输出: 4 解释: 最长的上升子序列是[2,3,7,101],
它的长度是4
以上来自leetcode链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/
这题的难点在于在O(nlogn)的时间复杂度解决它,这个题解给的方法解释都很难懂,看了很久以及看了好几个题解,才勉强理解,所以记录一下自己理解以后自己总结。
1.首先,看到这种题目想到的算法解决办法肯定是动态规划。
2.解题的主要算法思路是:建立一个保存子序列的DP数组,一个指针向前遍历,如果元素比子序列动态数组里最后一个元素都大,那就保存在后面,如果小于,那就在数组里找到那个比它大的数(最接近的)替换掉。
首先,这样一个算法思路保持着动态数组一直是单调的。简单证明:因为这样的添加元素方式一直是保持有序的。
其次,这样的思路原因是由于贪心算法,尽量先找一个小的元素保存下来,保持序列前面的元素尽量小,这样才可以让后面序列尽量长。然后查找元素的时候使用二分查找,就可以降低复杂度O(n)到O(logn),因为是单调有序的才可以使用二分查找。
最后,为什么这种算法思路可以奏效,一开始看肯定会很奇怪,就算知道使用了贪心思路。我们使用示例输入来模拟一下:
[10]->[9]->[2]->[2,5]->[2,3]->[2,3,7]->[2,3,7,101]->[2,3,7,18]
可以看到的是,每次都将较小的保留下来,能构成子序列的话,小的那个数是肯定能替代大的那个数的功能的,所以会尽量让子序列的元素小一点。
最后放出自己写的代码,可以互相参考。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> LIS_min;
if(nums.size()<=1) return nums.size();
LIS_min.push_back(nums[0]);
for(int i =0;i<nums.size();i++){
if(nums[i]>LIS_min[LIS_min.size()-1]){
LIS_min.push_back(nums[i]);
}
else{
int left = 0;
int right = LIS_min.size()-1;
int mid;
while(left<right){
mid = (left+right)/2;
if(LIS_min[mid]==nums[i]) { left = mid; break; }
else if(LIS_min[mid]<nums[i]) left = mid+1;
else right = mid;
}
LIS_min[left] = nums[i];
}
}
return LIS_min.size();
}
};