動態規劃問題(2)——尋找最長公共子串

題目

給定兩個字符串,求出它們的最長公共字串git

var str1="abcdefg";
var str2="xyzabcd";

說明:好比在單詞"abcdefg"和"abcdefg"它們的最長公共子序列是"abcd"。尋找最長子序列經常使用於遺傳學中,用於使用核苷酸鹼基的首字母對DNA的描述(這段話從網上抄的)。github

最長公共子串和最長公共子序列的區別。數組

最長公共子串和最長公共子序列的區別爲:子串是串的一個連續的部分,子序列則是從不改變序列的順序,而從序列中去掉任意的元素而得到新的序列;也就是說,子串中字符的位置必須是連續的,子序列則能夠沒必要連續。微信

這道題,想了很長時間,終於慢慢的把題目作出來了,接下來的內容可能會很難理解,也許我比較笨吧至少對我來講想了很久。我會盡可能結合圖文去展現解題的思路,也能夠本身想一想,而後在看接下來的內容。優化

暴力破解

其實看到這個問題咱們直接能夠用暴力的方式解決這個問題。給定兩個字符串A和B,咱們能夠經過從A的第一個字符開始與B對應的每個字符進行對比的方式找到最長的公共字串。若是此時沒有找到匹的字母,則移動到A的第二個字符處,而後從B的第一個字符處進行對比,以此類推。因爲下面的內容較多,爆力方法我這裏就不寫了。spa

動態規劃

咱們回顧一下動態規劃的解題思路:code

  1. 從底部開始解決問題,將全部小問題解決掉,而後合併成一個總體的解決方案。rem

  2. 使用一個數組創建一張表,用於存放被分解成衆多子問題的解。字符串

那基於這兩點咱們首先要分解這個問題,怎麼分解呢?源碼

第一步: 最小的是每一個字符,因此要把分解成單個的字符去匹配每一個單個的字符。所以這裏咱們能夠獲得下面這一張表:

字符串匹配

匹配爲1,不匹配爲0,這個時候咱們就分解成爲每一個字符的匹配狀況,由此獲得。

第二步: 每一個子問題有了,這個時候咱們要創建一個數組去存放每一個子問題的解。關鍵問題在於,咱們怎麼樣把每一個子問題的解存起來,而且能夠獲得咱們想要的結果。對錶作一些處理,初始化一個二維數組:

var arr = new Array(str1.length + 1);
for (var i = 0; i <= str1.length + 1; i++) {
    arr[i] = new Array(str2.length + 1);
    for (var j = 0; j <= str2.length + 1; j++) {
        arr[i][j] = 0;
    }
}

獲得以下的表:

image

圖中咱們能夠看到行和列都多加了一個而且都是0,這是爲了判斷上一個是否相等的操做,後面你就會明白它的意思了。

對初始化的二維數組坐些處理,獲得以下圖:

構建存放數組

一開始看到這個圖的時候你可能會很懵逼,若是你已經看明白了,那你就跳過我這段廢話吧。咱們應該一直的告訴本身,新建的這個數組是存放每一個子問題的解,首先要明確的是找出最長字串,有哪些方式能夠作到把字串從原來的字符串中截取,因此要知道起始位置和字串的長度。從圖中能夠看到的紅字就是存放這個位置的最優解字串的長度,因此應該創建兩個變量去存儲其實位置的index 和最大長度 maxLen 。獲得一下代碼:

var maxLen = 0;
var index = 0;
for(var i = 0; i <= str1.length; i++){
    for(var j = 0; j <= str2.length; j++){
        if(i == 0 || j == 0){
            arr[i][j] = 0
        }else{
            if (str1[i] == str2[j] && str1[i - 1] == str2[j - 1]) {
                arr[i][j] = arr[i - 1][j - 1] + 1;
            }else{
                arr[i][j] = 0;
            }
        }
        if(arr[i][j] > maxLen){
            maxLen = arr[i][j];
            index = i;
        }
    }
}

簡單的解釋一下,這裏要明確連續的字串的位置,是二維數組對角線上的值,這點明確了不少問題就好理解了。

上面的代碼把最主要的兩個參數找到了,那接下來就是選擇其中的一個字符串去截取字串:

var str = "";
if(maxLen == 0){
    return "";
}else{
    for(var k = index - maxLen; k < maxLen; k++){
        str += str1[k];
    }
    return str;
}

下面貼上完整的代碼,你也能夠去個人github上查看源碼;

function LCS(str1, str2){
    var maxLen = 0;
    var index = 0;

    var arr = new Array();
    for (var i = 0; i <= str1.length + 1; i++) {
        arr[i] = new Array();
        for (var j = 0; j <= str2.length + 1; j++) {
            arr[i][j] = 0;
        }
    }

    for(var i = 0; i <= str1.length; i++){
        for(var j = 0; j <= str2.length; j++){
            if(i == 0 || j == 0){
                arr[i][j] = 0
            }else{
                if (str1[i] == str2[j] && str1[i - 1] == str2[j - 1]) {
                    arr[i][j] = arr[i - 1][j - 1] + 1;
                }else{
                    arr[i][j] = 0;
                }
            }
            if(arr[i][j] > maxLen){
                maxLen = arr[i][j];
                index = i;
            }
        }
    }

    var str = "";
    if(maxLen == 0){
        return "";
    }else{
        for(var k = index - maxLen; k < maxLen; k++){
            str += str1[k];
        }
        return str;
    }
}
var str1="abcdefg";
var str2="xyzabcd";
LCS(str1, str2)     // abcd

到此爲止咱們終於找到了最長公共字串。到這裏咱們須要想一想能不能在優化了,雖然這樣解決問題,可是引入了一個二維數組,在兩個字符串都較大的狀況下不是很划算,是否能夠進一步優化?答案是能夠的,須要改變一下策略,是否能夠創建一個一維數組就能夠解決問題了呢,這裏晚了先寫這麼多把,以後我會在公衆號中貼出暴力解法和動態規劃的優化解法,歡迎持續關注個人公衆號得到一手的信息。

歡迎關注微信公衆帳號查看最新文章

歡迎關注微信公衆帳號查看最新文章

相關文章
相關標籤/搜索