求2个字符串的最长公共子序列和最长公共子字符串

一. 最长公共子序列

定义:

一个数列S,如果分别是两个多个已知数列子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列
例如:输入两个字符串BDCABAABCBDAB,字符串 BCBABDAB 都是是它们的最长公共子序列,则输出它们的长度4,并打印任意一个子序列. (Note: 不要求连续)

判断字符串相似度的方法之一 - 最长公共子序列越长,越相似。

思路:

穷举方法:
穷举法是解决最长公共子序列问题最容易想到的方法,即对S的每一个子序列,检查是否为T的子序列,从而确定它是否为ST的公共子序列,并且选出最长的公共子序列。

ST的所有子序列都检查过后即可求出ST的最长公共子序列。S的一个子序列相应于下标序列1,2,...,n的一个子序列。因此,S共有2^n个子序列。当然,T也有2^m个子序列。

因此,蛮力法的时间复杂度为O(2^n * 2^m),这是指数级别。

动态规划方法:

最优子结构性质:

设序列 X=<x1, x2, …, xm>Y=<y1, y2, …, yn> 的一个最长公共子序列 Z=<z1, z2, …, zk>,则:

  1. xm = yn,则 zk = xm = ynZk-1Xm-1Yn-1 的最长公共子序列;

    image.png

  2. xm ≠ yn, 要么ZXm-1Y 的最长公共子序列,要么 ZXYn-1 的最长公共子序列。

  • xm ≠ ynzk≠xm ,则 ZXm-1Y 的最长公共子序列;

  • xm ≠ yn 且 zk ≠yn ,则 ZXYn-1 的最长公共子序列。

综合一下:就是求二者的大者

image.png

递归结构容易看到最长公共子序列问题具有子问题重叠性质。例如,在计算 XY 的最长公共子序列时,可能要计算出 XYn-1Xm-1Y最长公共子序列。而这两个子问题都包含一个公共子问题,即计算 Xm-1Yn-1最长公共子序列

image.png

计算最优值:
子问题空间中,总共只有O(m*n)个不同的子问题,因此,用动态规划算法自底向上地计算最优值能提高算法的效率。

长度表C 和 方向变量B:

image.png

实现代码:

/**
 找出 两个 字符串 的公共 子序列(动态规划)
 @param str1 字符串1
 @param str2 字符串2
 */
void maxPublicSubSequence(char *str1, char *str2) {
    assert(str1 != NULL && str2 != NULL);
    
    // 字符串 1 长度
    int str1Length = strlen(str1);
    // 字符串 2 长度
    int str2Length = strlen(str2);
    // 开辟 二维 存储 数组 (并初始化 值为:0)
    
    int **postionArray = (int **)malloc(sizeof(int *) * (str1Length + 1));
    
    for (int i = 0; i <= str1Length; i ++) {
        postionArray[i] = (int *)malloc(sizeof(int) * (str2Length + 1));
    }
    
    for (int i = 0; i <= str1Length; i++) {
        for (int j = 0; j <= str2Length; j++) {
            postionArray[i][j] = 0;
        }
    }
    
    
    // 循环 遍历
    for(int i = 1; i <= str1Length; i++) {
        for(int j = 1; j <= str2Length; j ++) {
            // 如果 两个字符 相同
            if (str1[i - 1] == str2[j - 1]) {
                 postionArray[i][j] = postionArray[i - 1][j - 1] + 1;

            }
            // 如果 两个 字符 不同
            else {
               postionArray[i][j] = (postionArray[i - 1][j] > postionArray[i][j - 1]) ? postionArray[i - 1][j] : postionArray[i][j - 1];
            }
            
        }
    }
    
    for (int i = 0; i < str1Length; i++) {
        for (int j = 0; j < str2Length; j++) {
            printf("%d ", postionArray[i][j]);
        }
        printf("\n");
    }
    
    
    
    int i, j ;
    for (i = str1Length, j = str2Length; i >= 1  && j >= 1;) {
        if (str1[i - 1] == str2[j - 1]) {
            printf("%c ", str1[i - 1]);
            i --;
            j --;
        }
        else {
            if (postionArray[i][j - 1] > postionArray[i - 1][j]) {
                j--;
            }
            else {
                i --;
            }
        }
    }
    
    // 释放开辟数组
    for (int i = 0; i < str1Length; i++) {
        free(postionArray[i]);
    }
    free(postionArray);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        maxPublicSubSequenceTwo("ABCBDAB" , "BDCABA");
    }
    return 0;
}

二: 最长公共子串

题目:

给定两个字符串,求出它们之间最长的相同子字符串的长度。

思路:

穷举法:

给定两个字符串AB,我们可以通过从A的第一个字符开始与B对应的每一个字符进行对比的方式找到最长的公共字串。如果此时没有找到匹的字母,则移动到A的第二个字符处,然后从B的第一个字符处进行对比,以此类推。

实现代码:

#import <Foundation/Foundation.h>
/**
 找出 两个 字符串 的 最长 公共 子串 (穷举法)
 @param str1 字符串1
 @param str2 字符串2
 */
void maxPublicSubStringOne(char *str1, char *str2) {
    assert(str1 != NULL && str2 != NULL);
    
    // 起始 位置
    int startPosition = 0;
    // 公共 子串 长度
    int maxStringLength = 0;
    
    // 循环 遍历 所有 子字符串
    for (int i = 0; i < strlen(str1); i ++) {
        for (int j = 0; j < strlen(str2); j++) {
            // 如果 两个 字符 相等
            if(str1[i] == str2[j]) {
                // 继续 比较 后面的字符
                int k = 1;
                while (str1[i + k] == str2[j + k] && str1[i + k] != '\0' && str2[j + k] != '\0') {
                    k ++;
                }
                // 如果 k 大于 最长 字符串
                if (k > maxStringLength) {
                    // 公共 子串 长度
                    maxStringLength = k;
                    // 起始位置
                    startPosition = i;
                }
            }
        }
    }
    
    if(maxStringLength > 0) {
        for (int i = startPosition; i <= maxStringLength; i++) {
            printf("%c ", str1[i]);
        }
    }
}

动态规划-空间换时间

采用一个二维矩阵来记录中间结果,矩阵的横坐标字符串1的各个字符,矩阵的纵坐标字符串2的各个字符。

举例说明:假设两个字符串分别为"bab""caba" (当然我们现在一眼就可以看出来最长公共子串是"ba""ab")

   b  a  b

c  0  0  0

a  0  1  0

b  1  0  1

a  0  1  0

可以看出,矩阵的斜对角线最长的那个就对应着两个字符串的最长公共子串

不过在二维矩阵上找最长的由1组成的斜对角线也是件麻烦费时的事,可以用下面的方法改进:当要在矩阵是填1时让它等于其左上角元素加1

   b  a  b

c  0  0  0

a  0  1  0

b  1  0  2

a  0  2  0

这样矩阵中的最大元素就是最长公共子串的长度。另外,在构造这个二维矩阵的过程中由于得出矩阵的某一行后其上一行就没用了,所以实际上在程序中可以用一维数组来代替这个矩阵。

实现代码:

/**
 找出 两个 字符串 的公共 子串(动态规划)
 @param str1 字符串1
 @param str2 字符串2
 */
void maxPublicSubStringTwo(char *str1, char *str2) {
    assert(str1 != NULL && str2 != NULL);
    
    // 起始 位置
    int startPosition = 0;
    // 公共 子串 长度
    int maxStringLength = 0;
    // 字符串 1 长度
    int str1Length = strlen(str1);
    // 字符串 2 长度
    int str2Length = strlen(str2);
    // 开辟 二维 存储 数组 (并初始化 值为:0)
    
    int **postionArray = (int **)malloc(sizeof(int *) * str1Length);
    
    for (int i = 0; i < str1Length; i ++) {
        postionArray[i] = (int *)malloc(sizeof(int) * str2Length);
    }
    
    for (int i = 0; i < str1Length; i++) {
        for (int j = 0; j < str2Length; j++) {
            postionArray[i][j] = 0;
        }
    }
    
    
    // 循环 遍历
    for(int i = 0; i < str1Length; i++) {
        for(int j = 0; j < str2Length; j ++) {
            // 如果 两个字符 相同
            if (str1[i] == str2[j]) {
                // 判断 释放 是 边界 情况
                if (i == 0 || j == 0) {
                    // 边界 情况
                    postionArray[i][j] = 1;
                }
                // 非边界 情况
                else {
                    postionArray[i][j] = postionArray[i - 1][j - 1] + 1;
                }
                
                if (postionArray[i][j] > maxStringLength) {
                    maxStringLength = postionArray[i][j];
                    startPosition = i - postionArray[i][j] + 1;
                }
            }
            
        }
    }
    
    // 打印 字符串
    if(maxStringLength > 0) {
        for (int i = 0; i <= maxStringLength; i++) {
            printf("%c ", str1[startPosition + i]);
        }
    }
    // 释放开辟数组
    for (int i = 0; i < str1Length; i++) {
        free(postionArray[i]);
    }
    free(postionArray);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容