2019-10-29

求2個字符串的最長公共子序列和最長公共子字符串

一. 最長公共子序列

定義:

一個數(shù)列S疚顷,如果分別是兩個多個已知數(shù)列子序列曾沈,且是所有符合此條件序列中最長的,則 S 稱為已知序列的最長公共子序列
例如:輸入兩個字符串BDCABAABCBDAB基协,字符串 BCBABDAB 都是是它們的最長公共子序列嗅蔬,則輸出它們的長度4剑按,并打印任意一個子序列. (Note: 不要求連續(xù))

判斷字符串相似度的方法之一 - 最長公共子序列越長,越相似澜术。

思路:

窮舉方法:
窮舉法是解決最長公共子序列問題最容易想到的方法艺蝴,即對S的每一個子序列,檢查是否為T的子序列鸟废,從而確定它是否為ST的公共子序列猜敢,并且選出最長的公共子序列。

ST的所有子序列都檢查過后即可求出ST的最長公共子序列盒延。S的一個子序列相應(yīng)于下標(biāo)序列1,2缩擂,...,n的一個子序列添寺。因此胯盯,S共有2^n個子序列。當(dāng)然,T也有2^m個子序列。

因此,蠻力法的時間復(fù)雜度為O(2^n * 2^m)脑又,這是指數(shù)級別叉趣。

動態(tài)規(guī)劃方法:

最優(yōu)子結(jié)構(gòu)性質(zhì):

設(shè)序列 X=<x1, x2, …, xm>Y=<y1, y2, …, yn> 的一個最長公共子序列 Z=<z1, z2, …, zk>泞边,則:

  1. xm = yn,則 zk = xm = ynZk-1Xm-1Yn-1 的最長公共子序列君账;

    image
  2. xm ≠ yn繁堡, 要么ZXm-1Y 的最長公共子序列,要么 ZXYn-1 的最長公共子序列乡数。

  • xm ≠ ynzk≠xm 椭蹄,則 ZXm-1Y 的最長公共子序列;

  • xm ≠ yn 且 zk ≠yn 净赴,則 ZXYn-1 的最長公共子序列绳矩。

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

image

遞歸結(jié)構(gòu)容易看到最長公共子序列問題具有子問題重疊性質(zhì)。例如玖翅,在計(jì)算 XY 的最長公共子序列時翼馆,可能要計(jì)算出 XYn-1Xm-1Y最長公共子序列。而這兩個子問題都包含一個公共子問題金度,即計(jì)算 Xm-1Yn-1最長公共子序列应媚。

image

計(jì)算最優(yōu)值:
子問題空間中,總共只有O(m*n)個不同的子問題猜极,因此中姜,用動態(tài)規(guī)劃算法自底向上地計(jì)算最優(yōu)值能提高算法的效率。

長度表C 和 方向變量B:

image

實(shí)現(xiàn)代碼:

/**
 找出 兩個 字符串 的公共 子序列(動態(tài)規(guī)劃)
 @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);
    // 開辟 二維 存儲 數(shù)組 (并初始化 值為: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;
        }
    }

    // 循環(huán) 遍歷
    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 --;
            }
        }
    }

    // 釋放開辟數(shù)組
    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對應(yīng)的每一個字符進(jìn)行對比的方式找到最長的公共字串受扳。如果此時沒有找到匹的字母携龟,則移動到A的第二個字符處,然后從B的第一個字符處進(jìn)行對比勘高,以此類推峡蟋。

實(shí)現(xiàn)代碼:

#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;

    // 循環(huán) 遍歷 所有 子字符串
    for (int i = 0; i < strlen(str1); i ++) {
        for (int j = 0; j < strlen(str2); j++) {
            // 如果 兩個 字符 相等
            if(str1[i] == str2[j]) {
                // 繼續(xù) 比較 后面的字符
                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]);
        }
    }
}

動態(tài)規(guī)劃-空間換時間

采用一個二維矩陣來記錄中間結(jié)果,矩陣的橫坐標(biāo)字符串1的各個字符华望,矩陣的縱坐標(biāo)字符串2的各個字符层亿。

舉例說明:假設(shè)兩個字符串分別為"bab""caba" (當(dāng)然我們現(xiàn)在一眼就可以看出來最長公共子串是"ba""ab")

   b  a  b

c  0  0  0

a  0  1  0

b  1  0  1

a  0  1  0

可以看出,矩陣的斜對角線最長的那個就對應(yīng)著兩個字符串的最長公共子串立美。

不過在二維矩陣上找最長的由1組成的斜對角線也是件麻煩費(fèi)時的事,可以用下面的方法改進(jìn):當(dāng)要在矩陣是填1時讓它等于其左上角元素加1方灾。

   b  a  b

c  0  0  0

a  0  1  0

b  1  0  2

a  0  2  0

這樣矩陣中的最大元素就是最長公共子串的長度建蹄。另外碌更,在構(gòu)造這個二維矩陣的過程中由于得出矩陣的某一行后其上一行就沒用了,所以實(shí)際上在程序中可以用一維數(shù)組來代替這個矩陣洞慎。

實(shí)現(xiàn)代碼:

/**
 找出 兩個 字符串 的公共 子串(動態(tài)規(guī)劃)
 @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);
    // 開辟 二維 存儲 數(shù)組 (并初始化 值為: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;
        }
    }

    // 循環(huán) 遍歷
    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]);
        }
    }
    // 釋放開辟數(shù)組
    for (int i = 0; i < str1Length; i++) {
        free(postionArray[i]);
    }
    free(postionArray);
}

9人點(diǎn)贊

算法題學(xué)習(xí)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末痛单,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子劲腿,更是在濱河造成了極大的恐慌旭绒,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件焦人,死亡現(xiàn)場離奇詭異挥吵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)花椭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門忽匈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人矿辽,你說我怎么就攤上這事丹允。” “怎么了袋倔?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵雕蔽,是天一觀的道長。 經(jīng)常有香客問我宾娜,道長批狐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任碳默,我火速辦了婚禮贾陷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嘱根。我一直安慰自己髓废,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布该抒。 她就那樣靜靜地躺著慌洪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪凑保。 梳的紋絲不亂的頭發(fā)上冈爹,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機(jī)與錄音欧引,去河邊找鬼频伤。 笑死,一個胖子當(dāng)著我的面吹牛芝此,可吹牛的內(nèi)容都是我干的憋肖。 我是一名探鬼主播因痛,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼岸更!你這毒婦竟也來了鸵膏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤怎炊,失蹤者是張志新(化名)和其女友劉穎谭企,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體评肆,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡债查,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了糟港。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攀操。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖秸抚,靈堂內(nèi)的尸體忽然破棺而出速和,到底是詐尸還是另有隱情,我是刑警寧澤剥汤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布颠放,位于F島的核電站,受9級特大地震影響吭敢,放射性物質(zhì)發(fā)生泄漏碰凶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一鹿驼、第九天 我趴在偏房一處隱蔽的房頂上張望欲低。 院中可真熱鬧,春花似錦畜晰、人聲如沸砾莱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腊瑟。三九已至,卻和暖如春块蚌,著一層夾襖步出監(jiān)牢的瞬間闰非,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工峭范, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留财松,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓纱控,卻偏偏與公主長得像辆毡,于是被迫代替她去往敵國和親政敢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容