算法小题:最长递增子序列


前言

ok,这两天又研究了关于最长递增子序列相关的一些内容。记录在此,希望便人便己


问题描述

求一个序列的最长递增子序列,这样的子序列是允许中间越过一些字符的,即留“空”。
例如:序列A: 4 2 3 1 5 的最长递增子序列为 2 3 5,长度为 3 。

在这里插入图片描述


解法一

分析

这题其实和上次那个最大连续子序列和有点像,解法一的思路也和最大连续子序列和问题有些许联系。可以采用类似的解空间的思路。
对于给定的一个求解序列来说,假定其最终的答案是b0,b1,b2…bj(这里的b0,b1,b2…都是从原序列中抽取的递增值,其中bj是最后一个元素,也是最大的一个),那么可以肯定bj要么是a0,要么是a1,…要么是a(n-1).也就是说,最终求解的序列的最大值,要么是以a0为结尾的序列,要么是a1结尾的序列…

举个栗子,还是题目中的那个序列:最终的答案可以是以下几种:
(1)、以4为结尾的序列,只有一种:就是4
(2)、以2为结尾的序列,一种:2 (注意(4 2)不是递增的)
(3)、以3为结尾的序列,两种:3 或者(2 3)

所以我们可以确定一个条件,然后所有的条件都组合起来。还是一句话,这里并不是说要用暴力解法,全部都列举出来,只是用这种思路(先确定解空间的一个条件)来构造符合动态规划的性质和特点,也就是重复子问题和最优子结构。

经过上述的分析,我们可以用lcs[i] 表示 以第i个结点为递增序列的最后一个结点的所有序列中最长的序列长度。

说的有点拗口哦,以题目所给的序列作为例子,比如说lcs[2]就是以3为结尾的递增序列中长度最长的那个,以3位结尾的递增序列有两种3 和 2 3 ,最长的长度肯定就是2了。所以lcs[2] = 2.

构造了lcs[ ]之后,我们可以得到动态规划的很重要的一个属性,就是最优子结构性质。具体一点就是说,lcs[i] 的求解可以从lcs[j],(0<=j <= i-1) 得到,说的在透明些,也就是说,lcs[i]的求解不需要重复i元素之前的所有序列情况,只要用到前面记录的所有lcs[j] 就可以(0<=j <= i-1) ),这也是动态规划思想的一个精髓,用记表的方式避免了重复计算相同的单元

最终我们可以得到这样一个递推式:
在这里插入图片描述

好了递推式出来了,代码就不成问题了。

代码

//利用动态规划求解最长递增子序列
int LongIncSeq(int data[],int n)
{
    int lis[n] = {0};
    int longest = 0;

    for(int i = 0;i<n;++i)
    {
        int tmpLon = 0;         //记录以i为结束结点的,最长递增子序列
        for(int j = i-1;j>=0;--j)       //判断data[0]-data[i-1]所有比data[i]小的元素
        {
            if(data[i] > data[j])
            {
                if(lis[j] >tmpLon)      //取较大的值
                    tmpLon = lis[j];
            }
        }
        lis[i] = tmpLon+1;          //记录以i为尾部的最长的递增子序列
        if(lis[i]>longest)      //记录整体最长的
            longest = lis[i];
    }
    return longest;
}

注:以上算法的复杂度很容易看出来是O(N2)级别的


解法二

分析

解法二比较巧妙,照我看来,还是从某种程度上用了解空间的这种思维方式。它是这样分解解空间的:

假设递增子序列B是一个解,那么B长度可能的取值是
0,1,2,3,…, n-1. 这个挺容易理解的。
那么我可以在遍历元素的过程中,记录着长度为i的递增子序列最大元素的最小值。用maxElem[i]表示。

具体来说:
(1)、长度为1的递增子序列最大元素的最小值为maxElem[1]
(2)、长度为2的递增子序列最大元素的最小值为maxElem[2]
(3)、长度为2的递增子序列最大元素的最小值为maxElem[3]
… … …
比如说对于序列A = {1,-1,2,-3,4,-5,6,-7}
当i为4时,
长度为1的递增序列有:1,-1,2,-3 这四个,其中的最小值为-1
长度为2的递增序列有:(1,2),(-1,2),其中的最小值为2
长度为3的递增序列没有。

与此同时,还要记录一个遍历到当前位置时,产生的最大的递增子序列,也就是随时更新maxElem[]数组的大小,假设用变量longIncSeq表示。

在遍历的时候主要的操作是用当前元素A[i] 和maxElem[j](j>=0 && j<=longIncSeq ),进行比较 (当然是从后往前比较)。
如果A[i]大于某个maxElem[j],就说明可以形成一个以A[i]为最大元素的子序列。如果这个新形成的子序列长度大于longIncSeq,就更新longIncSeq的值。同时要更新对应长度子序列最大元素(也就是末尾元素)的最小值。

我知道,看起来写的有些复杂,真正动动你的小手指,模拟一下就基本清楚了。

代码

//另一种动态规划的解法:最长递增子序列
int LongIncseq(int data[],int n)
{
    int longIncSeq = 0;         //记录最长的递增子序列的长度
    int maxElem[n] = {0};       //记录长度为i的递增子序列最大元素(也就是序列的末尾元素)的最小值

    maxElem[0] = (*min_element(data,data+n))-1;  //最小元素减1
    for(int i = 0;i<n;++i)
    {
        int j;

        for( j= longIncSeq;j>=0;--j)        //寻找所有序列长度在longIncSeq—0之间
        {
            if(data[i] > maxElem[j])       //data[i]大于序列长度为j的最大元素
            {
                break;
            }
        }

        if(j == longIncSeq)
        {
            ++longIncSeq;

            maxElem[longIncSeq] = data[i];          //最新的序列长度
        }
        else if(maxElem[j]<data[i] && maxElem[j+1] > data[i])    //新长度为j+1的序列,其最大值小于原来的那个就更换
        {
            maxElem[j+1] = data[i];
        }
    }

    return longIncSeq;
}

总结

稍稍总结一下吧,两点内容:
(1)、动态规划算法针对的是问题的求解过程,而不是针对问题本身。我们在有了一个思路之后,可以按照这个思路,探究是否可以使用动态规划的思想,就我来看就是记表、以空间换时间。
(2)、分解解空间,先假设确定一个条件,或许可以作为上面的一种探究思路。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值