https://leetcode-cn.com/problems/longest-increasing-subsequence/
注意子序列不要求连续
一般这种最长啊,最大啊,不出意外就是dp了
根据之前的选择情况来更新当前的选择情况
一开始对着样例想了一个贪心
既然是求最长的子序列,那么我假设当前第一个子串就是数组的开头,并且使用一个变量来存储最长上升子序列的最后一个元素,记为R,一开始第一个R就是数组的开头
然后往后找一个最小的元素,这是分为两种情况
- 如果后面的最小元素大于当前的R,那么放心的加入子序列,同时更新R和最大长度,然后从新加入的元素的下标继续寻找最小的元素
- 如果后面的最小元素小于等于当前的R,那么当前子序列废弃,更新目前获得的最大长度,然后将这个最小元素设为新的子序列开头
对着这个样例很美好
[10,9,2,5,3,7,101,18]
直接找到2 3 7 18,长度为4
但是这种就不行了
[8,9,10,4]
直接从8跳到4了
dp
然后我就屈服于DP了,自己真的不会写DP(还是写得太少了),看了一眼题解
说一下思路
dp[i]表示以i下标结尾数组,其中最长的上升子序列长度
假设j<i,表示j下标位于i下标之前
那么dp[i]的选择就依赖于之前的最大值了,也就是max(dp[j]),0<=j<i
但是由于子序列要求上升,所以nums[i]必须要大于nums[j],这个时候dp[i]才能设置成max(dp[j])+1
但是当nums[i]<nums[j]的时候,并不是直接把dp[i]设置成1,因为它有可能继承其它最长上升子序列的长度
举个例子
nums:4 7 8 6
dp :1 2 3 2->(4,6)
所以我们的状态转移方程中的max不是标准库函数里面的max,而是要遍历之前的dp[j]才能找到尽可能的最大值
dp推完了那就是写代码了…难点就是怎么把状态转移方程推出来
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
# 最直观的想法:
if not nums:
return 0
maxsize=1
dp=[1]*len(nums)
for i in range(1,len(nums)):
for j in range(0,i):
if nums[j] < nums[i]:
dp[i]=max(dp[i],dp[j]+1)
return max(dp)
DP就是辣么🐂🍺,但是自己写不出
贪心+二分
这里先证明了一个最长上升子序列的性质
概括一下,我们要求的最长上升子序列要尽可能地长,意为序列的元素要尽可能地多
怎么才能尽可能地多呢
首先子序列肯定是有序的,有序肯定可以用二分
每当我们遍历到一个新元素,就尝试把它加入到序列中,如果当前的新元素比序列最后一个元素还要大,那么就直接加入,万事大吉,如果新元素没有最后一个元素大,我们就尝试在序列中找到第一个大于它的数(c++中的lower_bound函数),然后替换掉比它大的旧元素
为什么要这样做呢,因为如果我们能使一个序列中的元素尽可能地小,它就能加入更多的新元素对吧?
3 2 9 6 7
1: 3
2: 2 替换,因为2更小
3: 2 9 加入
4: 2 6 替换,6更小
5: 2 6 7 加入
很简单对吧?🤣
重要的是上面分析过程中贪心的思想,有序我们也可以自然的想到使用二分搜索
奉上leetcode官网的一条评论
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(!nums.size())
return 0;
vector<int>maxlen;
for(auto num:nums){
if(maxlen.size()==0 or num>maxlen.back())
maxlen.push_back(num);
else if(num<maxlen.back()){
// lower_bound(起始地址,结束地址,要查找的数值) 返回的是数值 第一个 出现的位置
// 即使不出现,lower_bound返回的是元素应该在数组中插入的位置
int ind=lower_bound(maxlen.begin(),maxlen.end(),num)-maxlen.begin();
maxlen[ind]=num;
}
}
return maxlen.size();
}
};
偷懒用了一下lower_bound
贴一下lower_bound的实现,功能是返回第一个大于等于x的元素的位置
需要注意的是while条件和>=的条件