写在前面:说来也巧,昨天刚在洛谷上做线性dp,在学习LIS和LCS的优化问题,今天每日一题就出了这道LIS。
思路:
首先说朴素算法的思路,
首先我们要定义一个集合,我们不妨以dp【i】表示数组中以第i位结尾的最长上升子序列的长度,然后从左往右依次迭代就可以了。
然后我们需要确定状态,dp【i】表示数组中以第i位结尾的最长上升序列长度,那么它和之前dp【0~i】有什么关系呢?如果nums【i】>nums【j】其中j=【0…i】那么dp【i】=dp【j】+1.至此,我们就确定了状态转移方程dp【i】=max(dp【j】+1,dp【i】)其中j是0到i且所有满足条件的下标。
代码如下:
/*class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.empty()) return 0;
if(nums.size()==1) return 1;
vector<int>dp(nums.size(),0);
for(int i=0;i<nums.size();i++)
{
dp[i]=1;
for(int j=0;j<i;j++)
if(nums[i]>nums[j])
dp[i]=max(dp[j]+1,dp[i]);
}
return *max_element(dp.begin(), dp.end());
}
};
上面这个算法很明显,时间复杂度是O(n^2)的。
然后我们考虑如何优化呢?
首先我们从上面那个算法可以知道,我们每次更新值是因为找到了前面可以更新的点,最后枚举找出最大的。
那么我们首先换一个思路,我们不妨定义dp【i】表示长度位i的上升子序列的最小结尾的数。
打个比方,比如第一个数为9,dp数组里面就放了一个【9】,他代表长度为1的子序列的最小结尾值为9,然后第二个数为2,2<9。所以2开始后面可能会有一个上升的序列,9后面也可能有一个上升的序列(有没有感觉像列车调度这道题)那么dp数组就更新为【2】表示当前长度为1的上升序列最小结尾为2.然后第三个数为4,4>2,这个是重点,表示4大于当前长度为1的上升序列的最小结尾,那么把他接到2后面长度就变成了2,所以dp数组里面就是【2,4】表示现在长度为1的上升序列最小结尾为2,长度为2的上升序列的最小结尾为4。如果这个数组里面就是9,2,4这三个数,那么此时程序已经运行完了,就直接输出dp数组的大小(就是2)就ojbk了。(是不是感觉越发的像列车调度这道题了。)
好,这个思路讲完了,我们总结一下,插入新的数的时候,(比如还是上面那个例子,我们紧接着要插入一个3,3比2大比4小所以要替换4的位置,如果是插入1,1比2小所以替换2的位置)。也就是说我们要在dp数组里找最接近插入的数的位置,这么经典的查找算法我们自然而然就想到了二分(因为dp数组里的数是严格单调递增的)。至此,优化完成!
代码如下:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> minnums;
for(int v : nums)
{
if(!minnums.size() || v > minnums.back())
minnums.push_back(v);
else
*lower_bound(minnums.begin(), minnums.end(), v) = v;
}
return minnums.size();
}
};