在之前的解法中运用的是动态规划,其时间复杂度为O(n2)。使用贪心和二分可以将时间复杂度降低。贪心的策略是:
- 首先定义两个数组,res保存最长递增子序列,mlen保存以元素 i 为结尾的最长递增子序列的长度
- 初始化时两个数组,res保存给定数组的第一个元素,mlen保存元素1,表示此时的长度为1.
- 遍历数组中其余的元素,如果遍历到的元素大于res数组最后一个元素,那么此时表明是递增,需要将该数字加入到res数组中,mlen加入以该元素为结尾的序列的长度,也就是res数组的长度加入到mlen 中。如果遍历到的元素小于res最后一个元素,此时需要在res数组中找第一个比arr[i]大的元素,将该元素替换为arr[i],由于已经找到了arr[i] 在res 中的位置,说明该位置代表了以 arr[i] 结尾的元素的长度,实际长度需要该下标加1,将该值加入到mlen数组中。
- 这样做是为了保存有更大的概率能够续接后面的元素。我认为res数组中保存的是多个递增子序列的一个合集,将这些子序列按照递增顺序排列好,每个位置取这一列的最小值。比如:递增序列集合为{[1,3],[1,2,4,5],[1,2,3]} 按照列合并得到res数组为[1,2,3,5]
- 注意此时res保存的并不是最后字典序最小的元素,需要另行寻找。在寻找过程中找的是最后一次出现相应长度的位置。比如给定arr=[1,2,8,6,4] 手工看到的结果是[1,2,4],选择mlen的下标为0,1,4;是不是与原始数组选择最长递增子序列的下标是一致的。原始数组选择下标为0,1,4即可得到[1,2,4]
res数组 | mlen数组 |
---|---|
1 | 1 |
1,2 | 1,2 |
1,2,8 | 1,2,3 |
1,2,6 | 1,2,3,3 |
1,2,4 | 1,2,3,3,3 |
- 用二分寻找第一个大于该元素的位置可以使用lower_bound(res.begin(), res.end(), arr[i]) - res.begin();
class Solution {
public:
int findmax(vector<int>& num,int k)
{
int l=0,r=num.size()-1;
while(l<r)
{
int mid=l+r>>1;
if(num[mid]<k) l=mid+1;
else r=mid;
}
return l;
}
vector<int> LIS(vector<int>& arr) {
// write code here
vector<int> res;
vector<int> mlen;
res.emplace_back(arr[0]);
mlen.emplace_back(1);
int n=arr.size();
for(int i=1;i<n;i++)
{
if(arr[i]>res.back())
{
res.push_back(arr[i]);
mlen.emplace_back(res.size());//注意不是mlen.back()+1,例如mlen数组[1,2,3,1,3]
}
else
{
int pos=findmax(res,arr[i]);
res[pos]=arr[i];
mlen.emplace_back(pos+1);
}
}
for (int i = arr.size()-1, j = res.size(); j > 0; i--) {
if (mlen[i] == j) {
res[--j] = arr[i];
}
}
return res;
}
};