单调递增最长子序列 (NYOJ 17) [动态规划]

单调递增最长子序列

时间限制:3000 ms  |  内存限制:65535 KB
难度:4
 
描述
求一个字符串的最长递增子序列的长度
如:dabdbf最长递增子序列就是abdf,长度为4
 
输入
第一行一个整数0<n<20,表示有n个字符串要处理
随后的n行,每行有一个字符串,该字符串的长度不会超过10000
输出
输出字符串的最长递增子序列的长度
样例输入
3
aaa
ababc
abklmncdefg
样例输出
    1
    3
    7
本题有多种解法:
一:最长公共子序列法
此方法用时较长,占用内存也较大,且仅适用于有规律且有限长度的子序列中,代码如下:
#include<stdio.h>
#include<string.h>
#define max(a,b) (a>b?a:b)
int dp[10010][30];
int main(void)
{
    char a[10010];
    char b[30]={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
    int n,len,i,j,k;
    int sum,max;    
    scanf("%d",&n);
    while(n--)
    {
        scanf("%s",a);
        len=strlen(a);        
        for(i=1;i<=len;i++)
        {
            for(j=1;j<=26;j++)
            {
                if(a[i-1]==b[j-1])
                {
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else
                {
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
/*      for(i=0;i<len;i++)
        {
            for(j=0;j<26;j++)
            {
                printf("%d ",dp[i][j]);
            }
            printf("
");
        }
*/        
        printf("%d
",dp[len][26]);        
    }
    
    return 0;
}

二:O(n^2) 最大子段和法

此方法采用了标记数组,记录每个当前状态下的最优解。

本题中的核心代码就是那两个for循环来更新m和dp数组中的值,

其中第一个循环中,每次都须将m置零 ,
其中第二个for循环的意思就是更新m的值,
使m的值为i位置字符之前的所有比i位置字符小的dp数组中所存值得最大值,直至第二个循环
结束,使i位置的dp数组为m的值加上1,即可。

代码如下:

#include<stdio.h>
#include<string.h>
char s[10010];
int dp[10010]={1};
int main(void)
{
    int n,len;
    int i,j,m;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%s",s);
        len=strlen(s);
        for(i=1;i<len;i++)
        {
            m=0;
            for(j=i-1;j>=0;j--)
            {
                if(s[i]>s[j]&&m<dp[j])
                {
                    m=dp[j];
                }
            }
            dp[i]=m+1;
        }
        m=dp[0];
        for(i=0;i<len;i++)
        {
            if(m<dp[i])
            {
                m=dp[i];
            }
        }
        printf("%d
",m);
    }
    return 0;
}

三:O(n^2)  此方法是方法二的优化,方法二中的标记数组过长,此方法采用标记字符串,所以标记数组的长度缩减到26即可,本代码中用的长度为30

本题中使用了标记字符串,先将待求字符串中的第一个字符赋到标记字符串中,
并使用k对标记字符串进行计数,

然后依次读入待求字符串的字符,
并与标记字符串中的字符从后往前依次比较,

若标记字符串的字符比待求字符串的字符大,且此时处在标记字符串的起始位置
,则将此位置的待求字符赋到标记字符串中,

若标记字符串的字符比待求字符串的字符小,则将此处待求字符串的字符赋到标记字符串的下一个字符,如果此时已达到标记字符串的长度,则令k加1,并结束当前循环,重新开始比较,重新开始时,只有k增加了一,其余各数据均不变。

代码如下:

#include<stdio.h>
#include<string.h>
int main(void)
{
    char s[10010];
    char dp[35];
    int n,len,i,j,k;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%s",s);
        len=strlen(s);
        dp[0]=s[0];
        k=1;
        for(i=0;i<len;i++)
        {
            for(j=k-1;j>=0;j--)
            {
                if(dp[j]>s[i]&&j==0)
                {
                    dp[0]=s[i];
                }
                if(dp[j]<s[i])
                {
                    dp[j+1]=s[i];
                    if(j==k-1)
                    {
                        k++;
                    }
                    break;
                }
            }
        }
        dp[k]='';
        printf("%d
",k);        
    }
    
    return 0;
}

四:O(n*lg n)

此方法是对方法三的优化,

最长递增子序列可以优化到 O(n*lg n)
通过优化方法二中的s[i]在dp[]什么位置,使用二分查找。
if(a[i]>dp[k]) {k=k+1;dp[k]=s[i];}
if(a[i]<dp[1]) dp[1]=s[i];
其他情况找到 dp[j]<s[i]<dp[j+1]  dp[j+1]=s[i];

注:虽然说此方法是最优的解法,但是在oj上面提交的时候,并没有方法三省时间,省内存。

#include<stdio.h>
#include<string.h>
#define N 10010
char s[N],dp[30];
int work(int n)
{
    int i,j,k,x,y,m;
    dp[0]=s[0];
    k=0;
    for(i=1;i<n;i++)
    {
        if(dp[k]<s[i])
        {
            k++;
            dp[k]=s[i];
            continue;
        }
        x=0;
        y=k;
        while(x<=y)
        {
            if(s[i]<dp[x])
            {
                j=x;
                break;
            }
            if(dp[y]<s[i])
            {
                j=y+1;
                break;
            }
            m=(x+y)/2;
            if(dp[m]<s[i])
            {
                x=m+1;
            }
            else if(s[i]<dp[m])
            {
                y=m-1;
            }
            else 
            {
                j=m;
                break;
            }
        }
        dp[j]=s[i];
    }
    return k+1;
}
int main(void)
{
    int n,i;
    scanf("%d",&n);    
    while(n--)
    {
        scanf("%s",s);
        printf("%d
",work(strlen(s)));
    }
    return 0;
}
原文地址:https://www.cnblogs.com/lbd_smile/p/4539044.html