LeetCode 筆記系列 20 Interleaving String [動態規划的抽象]


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

For example,
Given:
s1 = "aabcc",
s2 = "dbbca",

When s3 = "aadbbcbcac", return true.
When s3 = "aadbbbaccc", return false.

這個題目值得記錄的原因除了自己的解法沒有通過大集合以外,主要還在與對動態規划的理解。在這兩個月研究算法的過程中,我發現自己更傾向於直觀的理解,而抽象思維上相對較弱。我們以這道題做個例子。

直觀上,我看到該題,就會去想,s1取一部分,s2取一部分,然后再s1取一部分,反復知道匹配完成s3,算法去模擬這樣的操作。

而當s1和s3匹配了一部分的時候,剩下s1和剩下的s3與s2又是一個子問題。這樣很容易寫成一個遞歸,但是需要注意兩點:

1. 遞歸方法中,我們總是拿s1首先去匹配s3,如果不匹配,直接返回false。這樣做的原因是保持匹配是“交替”進行的;

2. 當出現既可以匹配s1,又可以匹配s2的時候,一樣可以通過遞歸來解決,看下面的代碼。

 1 private boolean isInterleaveInternal(String s1, String s2, String s3){
 2         if(s1.equals("")) {
 3             return s2.equals("") && s3.equals("");
 4         }
 5         if(s1.equals(s3) && s2.endsWith("")) return true;
 6         int i1 = 0;
 7         int i2 = 0;
 8         int i3 = 0;
 9         if(s1.charAt(0) != s3.charAt(0)) return false;
10         while(i1 < s1.length() && i2 < s2.length() && i3 < s3.length() &&
11                 s1.charAt(i1) == s3.charAt(i3)) {
12             i1++;
13             i3++;
14             //如果這里s2也可以匹配s3,那么我們立馬遞歸進行匹配
15             if(s2.charAt(i2) == s3.charAt(i3) && isInterleaveInternal(s2.substring(i2), s1.substring(i1), s3.substring(i3)))
16                 return true;
17         }
18         //接下來開始匹配s2
19         return isInterleaveInternal(s2, s1.substring(i1), s3.substring(i3));
20         
21     }
View Code

所以在調用這個方法的時候,也比較復雜,需要保證一定是s1首先匹配s3.

 1 public boolean isInterleave(String s1, String s2, String s3) {
 2         // Start typing your Java solution below
 3         // DO NOT write main() function
 4         if(s1.length() + s2.length() != s3.length()) return false;
 5         if(s1.equals("") || s2.equals("") || s3.equals("")) {
 6             if(s3.equals("")) return s1.equals("") && s2.equals("");
 7             else return s1.equals(s3) || s2.equals(s3);
 8         }
 9         if(s1.charAt(0) == s3.charAt(0)) {
10             if(s2.charAt(0) != s3.charAt(0)) {
11                 return isInterleaveInternal(s1, s2, s3);
12             }else {
13                 if(isInterleaveInternal(s1, s2, s3)) return true;
14                 else return isInterleaveInternal(s2, s1, s3);
15             }
16         }else if(s2.charAt(0) == s3.charAt(0)) return isInterleave(s2, s1, s3);
17         else return false;
18     }
View Code

這個辦法看上去蠻直觀的,是我馬上能想到的,而且也是收到前面遞歸方法的影響。

但是大集合會超時,而且不好的地方是主函數有挺多的條件判斷,顯得不夠簡潔。

於是我們參考了這里的動態規划方法。

動態規划矩陣matched[l1][l2]表示s1取l1長度(最后一個字母的pos是l1-1),s2取l2長度(最后一個字母的pos是l2-1),是否能匹配s3的l1+12長度。

那么,我們有

matched[l1][l2] = s1[l1-1] == s3[l1+l2-1] && matched[l1-1][l2] || s2[l2 - 1] == s3[l1+l2-1] && matched[l1][l2-1]

邊界條件是,其中一個長度為0,另一個去匹配s3.

這里s1和s2交替出現的規律並不明顯,所以沒有直觀地想到。

代碼如下:

 1 public boolean isInterleave2(String s1, String s2, String s3){
 2         if(s1.length() + s2.length() != s3.length()) return false;
 3         boolean[][] matched = new boolean[s1.length() + 1][s2.length() + 1];
 4         matched[0][0] = true;
 5         for(int i1 = 1; i1 <= s1.length(); i1++){
 6             if(s3.charAt(i1-1) == s1.charAt(i1-1)) {
 7                 matched[i1][0] = true;
 8             }else break;
 9         }
10         for(int i2 = 1; i2 <= s2.length(); i2++){
11             if(s3.charAt(i2 - 1) == s2.charAt(i2 - 1)) {
12                 matched[0][i2] = true;
13             }else break;
14         }
15         
16         for(int i1 = 1; i1 <= s1.length(); i1++){
17             char c1 = s1.charAt(i1 - 1);
18             for(int i2 = 1; i2 <= s2.length(); i2++){
19                 int i3 = i1 + i2;
20                 char c2 = s2.charAt(i2 - 1);
21                 char c3 = s3.charAt(i3 - 1);
22                 if(c1 == c3){
23                     matched[i1][i2] |= matched[i1 - 1][i2];
24                 }
25                 if(c2 == c3){
26                     matched[i1][i2] |= matched[i1][i2 - 1];
27                 }
28             }
29         }
30         return matched[s1.length()][s2.length()];
31     }
View Code

總結下:

1)遞歸能寫出比較清晰簡單的代碼,但是有比較高的時間復雜度;

2)在遞歸不滿足條件的情況下,動態規划是個比較好的選擇;

3)一般來說,獨立變量的個數決定動態規划的維度,例如l1和l2獨立變化,所以用了二維動態規划。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM