经典动态规划-01之最长上升(递增)子序列

最长上升子序列(LIS)的定义:

   一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).

三种求解方法:

1. O(n^2)的DP
2. O(nlogn)的二分+贪心法
3. O(nlogn)的树状数组优化的DP

题目1:求最长连续递增序列

给定一个未经排序的整数数组,找到最长且连续的的递增序列。

示例 1:

输入: [1,3,5,4,7]
输出: 3
解释: 最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为5和7在原数组里被4隔开。
示例 2:

输入: [2,2,2,2,2]
输出: 1
解释: 最长连续递增序列是 [2], 长度为1。
注意:数组长度不会超过10000。

思路:

dp[]数组表示以i结尾的最长连续递增序列的长度!!所以后面的元素如果比前面的元素大的话,那么他的dp值就是前面的dp值加一.不用管前面的值是如何计算出来的.只是拿来用就行了

注意这是连续的!!!!所以之需要一个for循环就行了

class Solution
{
  public:
    int findLengthOfLCIS(vector<int> &nums)
    {
        int len = nums.size();
        if (len <= 0)
            return 0;
        vector<int> dp(len, 1);
        int res = 1;
        for (int i = 1; i < len; i++)
        {
            if (nums[i - 1] < nums[i])
                dp[i] = dp[i - 1] + 1;
            if (dp[i] > res)
                res = dp[i];
        }
        return res;
    }
};

题目2:找到其中最长上升子序列的长度

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

示例:

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

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

感性思路:简单DP,不一样的配方,一样的套路。想要求得最长上升子序列的长度,来源只有一种——前一个最长上升子序列的长度+1 ,据此就得到了状态转移方程 。
如何实现:用F[i] 来表示该位置最长上升子序列的长度,那么我们就扫描i之前的元素,如果前面的元素比nums[i]小,我们就让该位置的长度=前面最大的最长上升子序列的长度加1,即“F[i]=max{F[j]+1}( 0< j < i, A[j] < A[i] )” 。需要注意的是必须是前面的最大的最长上升子序列的长度加1 !!! 比如:
1, 3, 6, 7, 9, 4, 10, 5, 6

对应的 F[ i ] 是:

1,2,3,4,5,3,  x

当算到 x 的时候,如果不是前面的最大的最长上升子序列的长度加1的话x的位置就会变成4,这是我们所不希望的 。这也就是不连续的最长递增子序列的特点,也就是需要两个for循环!!

class Solution
{
  public:
    int lengthOfLIS(vector<int> &nums)
    {
        if (nums.size() == 0)
            return 0;
        /**
         定义记录最长子序列长度的数组 array
         array[i] 表示以 nums[i] 结尾的最长子序列的长度值,对于每个元素,初始化都为 1
         */
        vector<int> array = vector<int>(nums.size(), 1);
        int res = array[0];
        for (int i = 1; i < nums.size(); i++)
        {
            for (int j = 0; j < i; j++)
            {
                //后面的元素比前面的元素值大,那么后面的元素可以放在前面的元素后面形成一个新的子序列
                if (nums[i] > nums[j])
                {
                    //更新记录的长度值
                    array[i] = max(array[i], 1 + array[j]);
                }
            }
           res = max(res,array[i]);
        }
        return res;
    }
};

题目3:求最长上升子序列的个数

给定一个未排序的整数数组,找到最长递增子序列的个数。

示例 1:

输入: [1,3,5,4,7]
输出: 2
解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。
示例 2:

输入: [2,2,2,2,2]
输出: 5
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。
注意: 给定的数组长度不超过 2000 并且结果一定是32位有符号整数。

思路:
  • 对于最长子序列的个数,需要我们在处理dp的时候来记录,我们设count[i]为以第i个数结尾的最长序列的个数,与dp同理,count初值也都是1;
  • 状态转移的时候,如果dp更新了,也就是说(dp[j]+1>dp[i])说明这个长度的序列是新出现的,我们需要将count[i]设置为count[j],因为新序列中,最新的数提供了序列的尾巴,数量是由前面积累的(或者说转移);举例序列[1 1 3 7]我们易得数字3对应的dp=2,count=2,因为可以构成两个[1 3]那么我们操作到数字7的时候,发现接在3后面最长,就可以转移count来作为初始数量;
  • dp[j]+1==dp[i]的时候,如同样例,操作7的时候,我们最先发现了可以接在5后面,最长序列[1 3 5 7],然后发现可以接在4后面,[1 3 4 7],长度也是4,这时候就同样需要转移count,加上去 count[i]+=count[j]
  • 最后我们需要遍历dp,找到dp[i]=我们记录的最大值的时候,累加我们得到的count[i],即为所求结果,时间复杂度是O(n^2)
class Solution
{
  public:
    int findNumberOfLIS(vector<int> &nums)
    {
        if (nums.size() <= 0)
            return 0;
        vector<int> dp(nums.size(), 1);    //表示以i结尾的子序列的长度
        vector<int> count(nums.size(), 1); //表示以i结尾的子序列的个数
        int res = 0;
        int max_len = 0;
        for (int i = 0; i < nums.size(); i++)
        {
            for (int j = 0; j < i; j++)
            {
                if (nums[j] < nums[i])
                {
                    //说明这个长度的序列是新出现的
                    if (dp[j] + 1 > dp[i])
                    {
                        count[i] = count[j];
                        dp[i] = dp[j] + 1;
                    }
                    //个数是递增的,后面的状态与上一个状态有关
                    else if (dp[j] + 1 == dp[i])
                        count[i] += count[j];
                }
            }
            max_len = std::max(max_len, dp[i]); //最长子序列的长度
        }
        for (int i = 0; i < nums.size(); i++)
        {
            if (dp[i] == max_len)
                res += count[i];
        }
        return res;
    }
};

参考链接:
https://blog.csdn.net/George__Yu/article/details/75896330

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值