动态规划解最长升序子串问题

来源:互联网 发布:央视网络春晚张信哲 编辑:程序博客网 时间:2024/05/24 07:20
一、题目重述
 
【问题描述】输入一行字符串,该字符串只由小写英文字母a-z组成,且其中的字符可以重复,最长不超过10000个字符。
 从该字符串中按顺序挑选出若干字符(不一定相邻)组成一个新串,称为“子串”。如果子串中每两个相邻的字符或者相等,或者后一个比前一个大,则称为“升序子串”。编程求出输入字符串的最长升序子串的长度。
 例如,由输入字符串abdbch可以构成的升序子串有:abd、abch、bbch、abbch等。其中最长的升序子串是abbch,其长度为5。
 【输入形式】从标准输入读取一行字符串,该串不含空格,以回车符结束。
 【输出形式】向标准输出打印一个正整数,是字符串中最长的升序子串的长度,在行末要输出一个回车符。
 【输入样例】abdbch
 【输出样例】5
 【样例说明】abdbch中最长子串是abbch,长度是5。
 【评分标准】结果完全正确得20分,每个测试点4分。上传c语言源程序为up.c。
 
二、算法介绍

    这道题大家都不太会做这个可以理解,当年我也没独立地做出来这道题。
    我们可以想象,如果能过穷举所有可能的子串,然后找出最大的那个不就可以了吗?假设我们的字符串有n个字符,那么所有的子串就有2^n那么多,也就是说穷举的时间复杂度是O(2^n),找最大值可以在穷举的过程中顺便进行,所以这个算法的时间复杂度就是O(2^n)。相信你一定可以想象,这个时间复杂度是十分糟糕的。
    说到这里,提一下“NP完全问题”。无论是通过理论还是通过实验,我们都发现对于一个指数时间复杂度的算法,即使是问题规模比较小的情况下也需要相当长的时间来求解,并且随着问题规模的增长,这个时间会飞速的上升。而对于一个多项式时间复杂度的算法,我们还是可以求解中等规模的问题的。于是有人发问:任何一个有解的问题是否都能过很快得到解呢?计算机科学家归纳出了若干个NP完全问题,只要有一个能过找到多项式时间复杂度的算法,那么就意味着所有的NP问题都有这样的算法,即NP=P。不管对这一问题的回答是什么,都会对计算机科学甚至人类世界有重要意义。如果回答是肯定的,那么带来的好处肯定是数不胜数;如果这个命题不成立,也不一定是个坏事,我们可以把更多的精力放在根据具体问题的特殊性,设计针对性的算法。聪明的你是不是已经在摩拳擦掌了。
    言归正传,上述算法肯定是不能通过我们的测试平台的。你现在肯定是想问,有没有一个多项式时间复杂度的算法呢?当然是有的,否则老师也不会出这道题了。这个算法就是利用了动态规划的思想。什么是动态规划?看起来好高端的样子。如果你有兴趣,可以去查阅资料深入了解,这里我们只就题论题。

我们设置一个数组dp,这个数组记录以字符串中某一个确定的字符为结尾的最长的升序子串的长度。
举个例子:
     a           b            d           b           c           h
dp[0]=1 dp[1]=2 dp[2]=3 dp[3]=3 dp[4]=4 dp[5]=5
假设现在我们已经知道了dp数组,那么dp的最大值就是所求了。
那么怎么求dp呢?我们还用这个例子来说明:
dp[0]显然为1,所以我们只要能建立后面与前面的关系就能得到所有的dp了。
还是上面的例子,dp[0]=1,接着算dp[1]。此时对应的字符是b,b肯定能接在a后面,所以dp[1]=dp[0]+1=2。
下一个是d,它可以皆在a后面也可以皆在b后面,按照dp的定义,我们选择接上以后最长的那个,所以dp[2]=max{dp[0],dp[1]} + 1 = 3
然后是b,它可以接在a、b后面,所以dp[3] = max{dp[0],dp[1]} + 1 = 3
c可以接在a、b、b后面(这两个b是不同的),所以dp[4] = max{dp[0],dp[1],dp[3]} + 1 = 4
h可以接在a、b、d、b、c后面,所以dp[5] = max{dp[0],dp[1],dp[2],dp[3],dp[4]} + 1 = 5

聪明的你肯定已经知道怎么做了吧。快去写你的第一个动态规划算法吧。

三、C语言实现

学长给你们一个可以跑的代码,建议先不要看。
#include <stdio.h>#include <string.h>#define MAXN 10000#define MAX(x,y) ((x)>(y) ? (x) : (y))    //一些小函数最好写成宏定义int main(){    char s[MAXN+1];    //存储输入自字符串    int i,j;           //循环控制变量    int dp[MAXN];       //记录以s[i]为结尾的所有升序子串的长度上确界    int max;    gets(s);    //设置动态规划初始值    dp[0] = 1;         //以s[0]为结尾的最长升序子串的上都肯定是1了    for(i=1;s[i]!='\0';i++)    //每一遍循环计算出一个dp[i]    {        dp[i] = 1;     //最差的情况是1        for(j=0;j<i;j++)      //遍历前面的所有dp,看哪个能接上,找到最大值        {            if(s[i]>=s[j])            {                if(dp[i] < dp[j] + 1)                {                    dp[i] = dp[j] + 1;                }            }        }    }    //所有dp的最大值即为所求    max = dp[0];    for(i=1;s[i]!='\0';i++)    {        max = MAX(max,dp[i]);    }    printf("%d\n",max);    return 0;}

四、最后的话

这个问题还是挺经典的,网上也有不少文章讲这个,如果没看懂我讲的,可以去看其他大神的。我提供的算法时间复杂度是O(n^2),这个问题还有O(n*logn)的算法,有兴趣可以去学习。
0 0
原创粉丝点击