[LeetCode] Interleaving String 交織相錯的字符串

 

Given s1s2s3, find whether s3 is formed by the interleaving of s1 and s2.html

Example 1:java

Input: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
Output: true

Example 2:數組

Input: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
Output: false

 

這道求交織相錯的字符串和以前那道 Word Break 的題很相似,就像我以前說的只要是遇到字符串的子序列或是匹配問題直接就上動態規劃 Dynamic Programming,其餘的都不要考慮,什麼遞歸呀的都是浮雲(固然帶記憶數組的遞歸寫法除外,由於這也能夠算是 DP 的一種),千辛萬苦的寫了遞歸結果拿到 OJ 上妥妥 Time Limit Exceeded,能把人氣昏了,因此仍是直接就考慮 DP 解法省事些。通常來講字符串匹配問題都是更新一個二維 dp 數組,核心就在於找出狀態轉移方程。那麼咱們仍是從題目中給的例子出發吧,手動寫出二維數組 dp 以下:函數

 

  Ø d b b c a
Ø T F F F F F
a T F F F F F
a T T T T T F
b F T T F T F
c F F T T T T
c F F F T F T

 

首先,這道題的大前提是字符串 s1 和 s2 的長度和必須等於 s3 的長度,若是不等於,確定返回 false。那麼當 s1 和 s2 是空串的時候,s3 必然是空串,則返回 true。因此直接給 dp[0][0] 賦值 true,而後若 s1 和 s2 其中的一個爲空串的話,那麼另外一個確定和 s3 的長度相等,則按位比較,若相同且上一個位置爲 True,賦 True,其他狀況都賦 False,這樣的二維數組 dp 的邊緣就初始化好了。下面只須要找出狀態轉移方程來更新整個數組便可,咱們發現,在任意非邊緣位置 dp[i][j] 時,它的左邊或上邊有可能爲 True 或是 False,兩邊均可以更新過來,只要有一條路通着,那麼這個點就能夠爲 True。那麼咱們得分別來看,若是左邊的爲 True,那麼咱們去除當前對應的 s2 中的字符串 s2[j - 1] 和 s3 中對應的位置的字符相比(計算對應位置時還要考慮已匹配的s1中的字符),爲 s3[j - 1 + i], 若是相等,則賦 True,反之賦 False。 而上邊爲 True 的狀況也相似,因此能夠求出狀態轉移方程爲:post

dp[i][j] = (dp[i - 1][j] && s1[i - 1] == s3[i - 1 + j]) || (dp[i][j - 1] && s2[j - 1] == s3[j - 1 + i]);優化

其中 dp[i][j] 表示的是 s2 的前 i 個字符和 s1 的前 j 個字符是否匹配 s3 的前 i+j 個字符,根據以上分析,可寫出代碼以下:url

 

解法一:spa

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        if (s1.size() + s2.size() != s3.size()) return false;
        int n1 = s1.size(), n2 = s2.size();
        vector<vector<bool>> dp(n1 + 1, vector<bool> (n2 + 1)); 
        dp[0][0] = true;
        for (int i = 1; i <= n1; ++i) {
            dp[i][0] = dp[i - 1][0] && (s1[i - 1] == s3[i - 1]);
        }
        for (int i = 1; i <= n2; ++i) {
            dp[0][i] = dp[0][i - 1] && (s2[i - 1] == s3[i - 1]);
        }
        for (int i = 1; i <= n1; ++i) {
            for (int j = 1; j <= n2; ++j) {
                dp[i][j] = (dp[i - 1][j] && s1[i - 1] == s3[i - 1 + j]) || (dp[i][j - 1] && s2[j - 1] == s3[j - 1 + i]);
            }
        }
        return dp[n1][n2];
    }
};

 

咱們也能夠把for循環合併到一塊兒,用if條件來處理邊界狀況,總體思路和上面的解法沒有太大的區別,參見代碼以下:code

 

解法二:orm

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        if (s1.size() + s2.size() != s3.size()) return false;
        int n1 = s1.size(), n2 = s2.size();
        vector<vector<bool>> dp(n1 + 1, vector<bool> (n2 + 1, false)); 
        for (int i = 0; i <= n1; ++i) {
            for (int j = 0; j <= n2; ++j) {
                if (i == 0 && j == 0) {
                    dp[i][j] = true;
                } else if (i == 0) {
                    dp[i][j] = dp[i][j - 1] && s2[j - 1] == s3[i + j - 1];
                } else if (j == 0) {
                    dp[i][j] = dp[i - 1][j] && s1[i - 1] == s3[i + j - 1];
                } else {
                    dp[i][j] = (dp[i - 1][j] && s1[i - 1] == s3[i + j - 1]) || (dp[i][j - 1] && s2[j - 1] == s3[i + j - 1]);
                }
            }
        }
        return dp[n1][n2];
    }
};

 

這道題也可使用帶優化的 DFS 來作,咱們使用一個 HashSet,用來保存匹配失敗的狀況,咱們分別用變量i,j,和k來記錄字符串 s1,s2,和 s3 匹配到的位置,初始化的時候都傳入0。在遞歸函數中,首先根據i和j,算出 key 值,因爲咱們的 HashSet 中只能放一個數字,而咱們要 encode 兩個數字i和j,因此經過用i乘以 s3 的長度再加上j來獲得 key,此時咱們看,若是 key 已經在集合中,直接返回 false,由於集合中存的是沒法匹配的狀況。而後先來處理 corner case 的狀況,若是i等於 s1 的長度了,說明 s1 的字符都匹配完了,此時 s2 剩下的字符和 s3 剩下的字符能夠直接進行匹配了,因此咱們直接返回二者是否能匹配的 bool 值。同理,若是j等於 s2 的長度了,說明 s2 的字符都匹配完了,此時 s1 剩下的字符和 s3 剩下的字符能夠直接進行匹配了,因此咱們直接返回二者是否能匹配的 bool 值。若是 s1 和 s2 都有剩餘字符,那麼當 s1 的當前字符等於 s3 的當前字符,那麼調用遞歸函數,注意i和k都加上1,若是遞歸函數返回 true,則當前函數也返回 true;還有一種狀況是,當 s2 的當前字符等於 s3 的當前字符,那麼調用遞歸函數,注意j和k都加上1,若是遞歸函數返回 true,那麼當前函數也返回 true。若是匹配失敗了,則將 key 加入集合中,並返回 false 便可,參見代碼以下:

 

解法三:

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        if (s1.size() + s2.size() != s3.size()) return false;
        unordered_set<int> s;
        return helper(s1, 0, s2, 0, s3, 0, s);
    }
    bool helper(string& s1, int i, string& s2, int j, string& s3, int k, unordered_set<int>& s) {
        int key = i * s3.size() + j;
        if (s.count(key)) return false;
        if (i == s1.size()) return s2.substr(j) == s3.substr(k);
        if (j == s2.size()) return s1.substr(i) == s3.substr(k);
        if ((s1[i] == s3[k] && helper(s1, i + 1, s2, j, s3, k + 1, s)) || 
            (s2[j] == s3[k] && helper(s1, i, s2, j + 1, s3, k + 1, s))) return true;
        s.insert(key);
        return false;
    }
};

 

既然 DFS 能夠,那麼 BFS 也就坐不住了,也要出來浪一波。這裏咱們須要用隊列 queue 來輔助運算,若是將解法一講解中的那個二維 dp 數組列出來的 TF 圖看成一個迷宮的話,那麼 BFS 的目的就是要從 (0, 0) 位置找一條都是T的路徑通到 (n1, n2) 位置,這裏咱們還要使用 HashSet,不過此時保存到是已經遍歷過的位置,隊列中仍是存 key 值,key 值的 encode 方法跟上面 DFS 解法的相同,初始時放個0進去。而後咱們進行 while 循環,循環條件除了q不爲空,還有一個是k小於 n3,由於匹配完 s3 中全部的字符就結束了。而後因爲是一層層的遍歷,因此要直接循環 queue 中元素個數的次數,在 for 循環中,對隊首元素進行解碼,獲得i和j值,若是i小於 n1,說明 s1 還有剩餘字符,若是 s1 當前字符等於 s3 當前字符,那麼把 s1 的下一個位置 i+1 跟j一塊兒加碼算出 key 值,若是該 key 值不在於集合中,則加入集合,同時加入隊列 queue 中;同理,若是j小於 n2,說明 s2 還有剩餘字符,若是 s2 當前字符等於 s3 當前字符,那麼把 s2 的下一個位置 j+1 跟i一塊兒加碼算出 key 值,若是該 key 值不在於集合中,則加入集合,同時加入隊列 queue 中。for 循環結束後,k自增1。最後若是匹配成功的話,那麼 queue 中應該只有一個 (n1, n2) 的 key 值,且k此時等於 n3,因此當 queue 爲空或者k不等於 n3 的時候都要返回 false,參見代碼以下:

 

解法四:

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        if (s1.size() + s2.size() != s3.size()) return false;
        int n1 = s1.size(), n2 = s2.size(), n3 = s3.size(), k = 0;
        unordered_set<int> s;
        queue<int> q{{0}};
        while (!q.empty() && k < n3) {
            int len = q.size();
            for (int t = 0; t < len; ++t) {
                int i = q.front() / n3, j = q.front() % n3; q.pop();
                if (i < n1 && s1[i] == s3[k]) {
                    int key = (i + 1) * n3 + j;
                    if (!s.count(key)) {
                        s.insert(key);
                        q.push(key);
                    }
                }
                if (j < n2 && s2[j] == s3[k]) {
                    int key = i * n3 + j + 1;
                    if (!s.count(key)) {
                        s.insert(key);
                        q.push(key);
                    }
                }
            }
            ++k;
        }
        return !q.empty() && k == n3;
    }
};

 

參考資料:

https://leetcode.com/problems/interleaving-string/

https://discuss.leetcode.com/topic/7728/dp-solution-in-java

https://discuss.leetcode.com/topic/3532/my-dp-solution-in-c 

https://discuss.leetcode.com/topic/30127/summary-of-solutions-bfs-dfs-dp

https://discuss.leetcode.com/topic/3436/my-accepted-java-recursive-solution-for-interleaving-string

 

LeetCode All in One 題目講解彙總(持續更新中...)

相關文章
相關標籤/搜索