LIS最长上升子序列总结

为了让自己更好的记住学过的知识,我要写博客,将做题中用到的一些方法和一类问题进行总结。

今天做题,碰到了一题关于最长上升子序列的题目,先上题

牛牛现在有一个n个数组成的数列,牛牛现在想取一个连续的子序列,并且这个子序列还必须得满足:最多只改变一个数,就可以使得这个连续的子序列是一个严格上升的子序列,牛牛想知道这个连续子序列最长的长度是多少。

输入描述:

输入包括两行,第一行包括一个整数n(1 ≤ n ≤ 10^5),即数列的长度;
第二行n个整数a_i, 表示数列中的每个数(1 ≤ a_i ≤ 10^9),以空格分割。

输出描述:

输出一个整数,表示最长的长度。

输入例子:

6 
7 2 3 1 5 6

输出例子:

5

对于这类笔试题,目前我也只学习了动态规划、双指针、递归、二分、哈希表、单调队列几个基础方法,具体问题具体分析的能力还得多多锻。用了双指针,可惜只有20%的样例通过,之前没做过LIS的题目,今天先总结一下最长子序列问题(LIS),主要也是以LeetCode上的题目展开,让读到这篇文章的人尽可能的将此问题理解的深点。

LeetCode 300. 最长上升子序列

问题描述

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:

  • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
  • 你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

解决方法
DP动态规划—O(n^2)、二分+贪心法—O(nlogn)、树状数组优化的DP—O(nlogn)

结题思路

最长上升子序列包含的意思

  1. 子序列元素在原数组中的下标是有序的,但不是连续的;
  2. 子序列元素值是上升的。

主要讲一下dp方法,用dp[i]表示以第i个元素结尾的长上升子序列得长度,例如

dp[0]表示[10]的结果是1;

dp[1]表示[10,9]的结果还是1;

dp[2]表示[10,9,5]的结果还是1;

dp[3]表示[10,9,2,5]的结果是2;

dp[4]表示 [10,9,2,5,3]的结果还是2;

dp[5]表示 [10,9,2,5,3,7]的结果是3;

dp[i]的结果与d[0]~dp[i-1]都有关,我们假设0<=j<=i-1;那么我们能够得到

dp状态转移方程:dp[i]=max(dp[i],dp[j]+1);

注意的一点是初始化dp[i]=1;dp的时间复杂度是O(n^2),因此不算最优。

二分+贪心法—O(nlogn)、树状数组优化的DP—O(nlogn)的思路大家可参考LeetCode 300 。

C++代码

#include <iostream>
#include <string>
#include <vector>

using namespace std;
int main()
{
    int n;
    cin>>n;
    
    vector<int> res(n);
    for(int i=0;i<n;i++)         
        cin>>res[i];
    
    int ans=0;
    vector<int> dp(n,1);
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<i;j++)  //j代表前res[0]~res[i-1]
        {
            if(res[i]>res[j])  //res[i]>res[j]说明相对于以j结尾的数组的最长子序列又可以增1啦
                dp[i]=max(dp[i],dp[j]+1);   
        }
        ans=max(ans,dp[i]);
    }
    cout<<ans<<endl;
}

LeetCode 128. 最长连续序列

问题描述

给定一个未排序的整数数组,找出最长连续序列的长度。

要求算法的时间复杂度为 O(n)。

示例:

输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。

解决方法
利用哈希表unordered_set

结题思路

最长上升子序列包含的意思

  1. 子序列元素在原数组中的下标是无序的;
  2. 子序列元素值是递增加1的。

这题的难点在于规定了时间复杂度为O(n),那么排序肯定是用不上了。

这里主要讲一下用unordered_set方法,LeetCode上用的都是unordered——map<int,int>,感兴趣的可以去查看一下。

解题思路主要是先将数据放到哈希表中。遍历哈希表,对每一个数据左右两边进行查找哈希表,用到了哈希表的count()函数,存在返回>0,反之返回0。知道左右查查找完毕,且每查找到一个数,则都要将其在哈希表中删除。这样才能保证所有的元素只遍历一次,复杂度是O(n)。

C++代码

#include <iostream>
#include <algorithm>
#include <unordered_set>
using namespace std;
int main()
{
    int n;
    cin>>n;

    unordered_set<int> myhash;   //建立哈希表
    int value;
    for(int i=0;i<n;i++)
    {
        cin>>value;
        myhash.insert(value);
    }

    int ans=1;
    for(auto c:myhash)
    {
        int temp=1;
        for(int j=c-1;myhash.count(j);j--)  //向左遍历
        {
            myhash.erase(j);
            temp++;
        }
        for(int j=c+1;myhash.count(j);j++) //向右遍历
        {
            myhash.erase(j);
            temp++;
        }
        //myhash.erase(c); 不能删除自己,会段错误。
        ans=max(ans,temp);
    }
    cout<<ans;
}

LeetCode 845. 数组中的最长山脉

问题描述

我们把数组 A 中符合下列属性的任意连续子数组 B 称为 “山脉”:

  • B.length >= 3

  • 存在 0 < i < B.length - 1 使得 B[0] < B[1] < … B[i-1] < B[i] > B[i+1] > … > B[B.length - 1]

    (注意:B 可以是 A 的任意子数组,包括整个数组 A。)

给出一个整数数组 A,返回最长 “山脉” 的长度。

如果不含有 “山脉” 则返回 0。

示例 1:

输入:[2,1,4,7,3,2,5]
输出:5
解释:最长的 “山脉” 是 [1,4,7,3,2],长度为 5。

示例 2:

输入:[2,2,2]
输出:0
解释:不含 “山脉”。

提示:

​ 0 <= A.length <= 10000
​ 0 <= A[i] <= 10000

解决方法
利用哈希表unordered_set

结题思路

这题的难度不大,不过是最接近牛牛那题的,算是简单版,难的题目其实也都是从简单的过来的,那么难题的解题方法也是从小的方法合起来解决的,所以多多积累知识点还很重要的。

言归正传,这题的主要解法是用双指针,和上一题有点类似,不过这个不用删除元素。因为约束条件要求山脉的长度>3,那么我们的遍历就从:1<=i<=n-2;

C++代码

#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <vector>
using namespace std;
int main()
{
    int n;
    cin>>n;
    vector<int> res(n);
    for(int i=0;i<n;i++)
        cin>>res[i];

    if ( n< 3)  return 0;

    int ans = 1;
    for (int i = 1; i < res.size() - 1; i++) {
        if (res[i] > res[i - 1] && res[i] > res[i + 1]) {
            int l = i - 1;
            int r = i + 1;
            while (l > 0 && res[l - 1] < res[l]) {
                l--;
            }
            while (r < res.size() - 1 && res[r] > res[r + 1]) {
                r++;
            }
            ans = max(ans, r - l + 1);
        }
    }
    cout<<ans<<endl;

}



这三个题目是LIS问题中比较基础的题目,但是笔试一般不会这么简单,像牛牛这题,就加了一个限制条件—最多只改变一个数,那么这题的解法就变困难些,那么这题该如何解呢?

解题思路

空间换时间,额外建立两个数组,一个left,一个right

left存放得是以第i个元素为结尾点自左向右的最长连续上升子序列(下标连续,值递增)

right存放得是以第i个元素为起始点自左向右的最长连续上升子序列(下标连续,值递增)

以[7 2 3 1 5 6]为例

left[0]=1,right[5]=1

left[1]=1,right[4]=2

left[2]=2,right[3]=3

left[3]=1,right[2]=1

left[4]=2,right[1]=2

left[5]=3,right[0]=1

用的是dp动态规划

状态转移矩阵

​ left[i]=nums[i]>nums[i-1]?left[i-1]+1:1;

​ right[i]=nums[i]<nums[i+1]?right[i+1]+1:1;

然后得到dp[i]=max(left[i]+right[i]-1);

C++代码

#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <vector>
using namespace std;
int main()
{
    int n;
    cin>>n;
    vector<int> res(n);
    for(int i=0;i<n;i++)
        cin>>res[i];

    vector<int> left(n);
    vector<int> right(n);

    left[0]=1;
    for(int i=1;i<n;i++)
        left[i]=res[i]>res[i-1]?left[i-1]+1:1;

    right[n-1]=1;
    for(int i=n-2;i>=0;i--)
        right[i]=res[i]<res[i+1]?right[i+1]+1:1;

    int ans=0;
    for(int i=0;i<n;i++)
    {
        ans=max(right[i],max(left[i],ans));
        if(i>0 && i<n-1 && res[i+1]-res[i-1]>=2)
            ans=max(ans,right[i+1]+left[i-1]+1);
    }
    cout<<ans;
    return ans;
}

以上就是自己对LIS问题的总结,都是一些解决基础问题的方法总结,最近经常用到的方法是动态规划、双指针、哈希表,下次总结一点双指针的问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值