最长公共子字符串

关于题目理解,请注意和最长公共子序列的区别,最长公共子字符串的解法是动态规划,但是比较难想到表的构造方法。

注意到,设给定字符串为str1 和 str2 ,二者的长度分别是 len1 和 len2 ,那么解空间大小之多是len1*len2?(假设最长公共子字符串为substr_common,那么substr_common在str1 中的结束位置或者起始位置只有len1种选择,而在str2中则最多len2种选择,故解空间最大为len1*len2)。对于解空间中的每一个解都对应着自己的长度,因为一旦结束位置都确定,即意味着公共子字符串找到了。

如果用蛮力法做的话,遍历解空间需要时间复杂度为O(len1*len2),然后确定每个解的长度需要的时间是O(min(len1,len2)),所以基本上是个O(n3)的算法,显然不妥。

 

前面说过,这道题可以用动态规划来做,动态规划最关键的是找到最优子结构,一般来说,最优子结构意味着问题可以找到一种递归的,不断缩小问题规模的解决方式,与分治法不同,动态规划的小问题之间有重叠,但是这个问题好像不是那么好找到一种递归的表达形式。

要缩小问题规模,一种最简单的想法肯定是缩小str1规模,或者缩小str2规模,或者二者同时缩小,而缩小的方式肯定是增减元素了,能用动态规划做必定有个自底向上的过程,很少有直接二分的, 直接二分是分治法的策略,所以增减一般在头尾增减,以此题为例,为保持一致性,在分析题目的过程设两个字符串都由尾部向头减少,而在解决问题的过程中是由头部向尾部增加。我们可以构建一张表count[len1][len2] , 其中count[i][j] 表示如果公共子字符串在str1的第i个位置结束,在str2的第j个位置结束时公共子字符串的长度。从底向上开始构建这张表,下面是增减的描述:假设目前需要计算count[i][j],有以下几种情况:

1. str1[i] == str2[j] : 此时count[i][j] = count[i-1][j-1]+1

2. str1[i] != str2[j] : 很显然,以i,j为结束点的解 count[i][j] = 0 .

因此,一旦知晓了count[i-1][0,….,len2] ,count[i][0,….,len2]可以顺序获得,以str1 = “1234”, str2 = “2345”为例,表的构造方式如下:

image

只要在构建表的过程中记住当前最长的子字符串长度和结束位置,就可以很轻易地打印出最长公共子字符串,与之相对应的代码如下:

/*
    author : lipan
    date : 2013.07.29
    email : areslipan@163.com
*/
#include <stdio.h>
#include <stdlib.h>
 
int lcs_substring(char *str1,char *str2,int str1_len,int str2_len);
int main()
{
    int str1_len = 0;
    int str2_len = 0;
 
    printf("Please input the length of two sequence : ");
    scanf("%d%d",&str1_len,&str2_len);
    char *str1 = (char *)malloc((str1_len)*sizeof(char));
    char *str2 = (char *)malloc((str2_len)*sizeof(char));
    printf("Please input the two sequence:\n");
    scanf("%s%s",str1,str2);
    lcs_substring(str1,str2,str1_len,str2_len);
    return 0;
}
 
int lcs_substring(char *str1,char *str2,int str1_len,int str2_len)
{
    //动态规划表
    char *record  = (char *)malloc(str2_len*str1_len*sizeof(char));
    //分别记录当前最长的子字符串长度,截止位置
    int maxSubStrLen = 0;
    int maxSubStrH = 0;
    int maxSubStrW =0;
    for(int h =0;h<str1_len;h++)
    {
        for(int w=0;w<str2_len;w++)
        {
            if(str1[h]==str2[w])
            {
                if(h == 0||w == 0)
                {
                    record[h*str1_len+w]=1;    
                }
 
                else
                {
                    record[h*str1_len+w] = record[(h-1)*str1_len+w-1]+1;
                }
                if(maxSubStrLen<record[h*str1_len+w])
                {
                    maxSubStrLen = record[h*str1_len+w];
                    maxSubStrW= w;
                    maxSubStrH= h;
                }
            }
        }
    }
    
    //输出最长公共子字符串
    printf("the longest common substring is :");
    for(int i = maxSubStrW-maxSubStrLen+1;i<=maxSubStrW;i++)
    {
        printf("%c",str2[i] );
    }
    return maxSubStrLen;
}
posted @ 2013-07-29 21:25  曾见绝美的阳光  阅读(549)  评论(0编辑  收藏  举报