【C】最长递增子串(LIS)求解

下文中提到的子串/子序列可能会有不同的理解,由于网上绝大部分人认为Subsequence为子序列,Substring为子串,所以以下内容都为子序列,如果本文发生歧义或者我有笔误的话,请一律将以上两个词语视为Subsequence
LIS(Longest Increasing Subsequence)最长递增子序列问题是一道非常基础的DP问题,这里我们主要是为了给各位解释解决DP问题的最重要的方法之一——记忆化搜索,这里仅讲解复杂度为O(N2)的方法解决此问题,并且这里我们只需要求出最长子序列长度,对具体是哪条子序列也不做讨论,本文最后会附上O(NlogN)的解决问题的代码,但不做过多的解释说明。

我们先把定义弄清楚

什么是子序列?

在原来的串中删掉一部分数据,并把剩下的数据按照原来的顺序排列组成的一个串称为该串的子序列。
例如对于1、2、3、4、5、6、7、8、9这个串,1、2、3是它的子序列,1、3、9是它的子序列、1、2、3、4、5、6、7、8、9也是它的子序列(子序列可以为它本身),但是诸如1、4、3或1、3、3或1、9、2、3、4、5、6、7、8都不是该串的子序列。

什么是递增子序列?

这个很好理解,即此子序列按照顺序递增。在上面那个示例中,它的所有子序列都是递增的(因为这个串本身就是递增的串,所以它的所有子序列都是递增的)。

什么是最长?

这个就不用解释了吧,上述例子中,它本身就是最长子序列

下面的讨论过程中,我们采用
1、7、3、4、5、9、8、9
这串数列

这里最长递增子序列为1、3、4、5、8、9

分析问题

1、我们假设这串数列只有第一个数据(即只有1)
那么我们可以确定,最长子序列长度为1。
2、这个时候我们加入第二个数据(即为1、7)
我们发现,7>1,也就是说,可以加入到这个答案之中,即最长子序列长度为2
3、这时候我们加入第三个数据——3,我们发现了一个问题,如果要把3加入到最长串中,则必须抛弃掉7,组成1、3串。但无论是哪种情况(1、7还是1、3)都是长度为2的子序列。这个长度为2是怎么判断出来的?我们需要好好考虑一下,即如何用计算机语言解释这个人脑的神奇思维过程。
我们可以这样假设,1前面还有n个数据,1为第n+1个数、7是n+2、3是n+3.
这个时候,按照我们前面这样做,3前面的数据已经组成很多的递增串了,而如果3需要加入到前面这个“大家庭”中,则需要找到一个串,其最末尾的数据小于3。
那我们如果要找到一个对3而言最优的串(即最长的串)那么就要在所有3可以加入的串中,找到最长的串。(如果没有可以加入的串,长度即为1)
那么我们可以把前面的每一个数据/元素都写成一个串,即当我们考虑到此数据后,就把满足此数据的最优的串保存下来,然后让下一个数据去这些串内找最优的,并给这个新的数据附上一个新的串。
例如,下一个数据:4,在这之前我们有了以下这些串:1和1、7和1、3这样三个串(第一个串只有一个数据1,因为对于1而言,前面没有串,所以自行成串,而对于其他两个数据,就没有单独的串了,原因:比如7,如果某个数据可以加入到7这个串末尾,则必定可以加入1、7这个串末尾),那么很明显,4可以加入到串为1或1、3,按照最优原则,则加入到1、3这个串中,组成1、3、4这个串,并保存。以此类推,则可以得到如下表格。

示例 1 7 3 4 5 9 8 9
此数据对应的最长串 1 1、7 1、3 1、3、4 1、3、4、5 1、3、4、5、9 1、3、4、5、8 1、3、4、5、8、9

这里我们在比较时注意到一个细节,一个串唯一有意义的两个数据分别是:最后一个数据和当前长度。那么也就是说,我们保存的时候可以无视前面已经确定的串,只需要考虑最后一位的大小是否小于当前比较的参数。由于我们是把串保存在当前数下的,所以这个串的最后一个数等于这个数(上面的表格中可以看出,下面的串的最后一个值必定等于上面的数据)
那么表格可以简化为如下:

示例 1 7 3 4 5 9 8 9
最长的串长度 1 2 2 3 4 5 5 6

那么我们只需要最后查找一下最大的数据是多少即可。
这里就直接给出代码

#include

int main()
{
    int n,a[1005],i,j,f[1005],maxn;//f用于保存上面表格的第二行
    maxn=0;
    scanf("%d",&n);//输入原来的串长度
    for (i=0; i<n; i++)
    {
        scanf("%d",&a[i]);//保存串
    }
    for (i=0; i<n; i++)
    {
        f[i]=1;//最短的情况也是1
        for (j=i-1; j>=0; j--)
        {
            if (f[j]+1>f[i] && a[j]<a[i])//如果当前判断的串长度+1比现在的串长,并且最末尾的数据小于当前数据
            {
                f[i]=f[j]+1;
            }
        }
    }
    for (i=0; i<n; i++)//这里找出f中的最大值
    {
        maxn=maxn>f[i]?maxn:f[i];
    }
    printf("%d\n",maxn);//输出
    return 0;
}

这里我们用到了一个很重要的思想——记忆化搜索。即对当前判断的对象前面的数据整理完之后保存下重要的数据用于下一次判断。

最后,我给出另外一种可能理解较难的方法,思想仍是记忆化搜索,但是复杂度只有O(NlogN),这里就不对该代码做过多解释了。

#include

int maxn[1005],maxlen;

int finddate(int l,int r,int t)
{
    if (l==r)
    {
        return r;
    }
    int m=(l+r)/2;
    if (maxn[m]>t)
    {
        return finddate(l, m, t);
    }
    else
    {
        return finddate(m+1, r, t);
    }
}

int main()
{
    int n,a[1005],i;
    maxlen=1;
    scanf("%d",&n);
    for (i=0; i<n; i++)
    {
        scanf("%d",&a[i]);
    }
    maxn[0]=a[0];
    for (i=1; i<n; i++)
    {
        if (maxn[maxlen-1]<a[i])
        {
            maxn[maxlen]=a[i];
            maxlen++;
        }
        else
        {
            maxn[finddate(0,maxlen-1,a[i])]=a[i];
        }
    }
    printf("%d\n",maxlen);
    return 0;
}

你可能感兴趣的:(【C】最长递增子串(LIS)求解)