1. 題目描述
給定三個字符串 s1, s2, s3, 驗證 s3 是否是由 s1 和 s2 交錯組成的。
示例 1:
示例 2:
2. 解題思路
s1 , s2 組成 s3 過程中不能改變 s1, s2 的字符順序,示例2中 s1, s2 字符和與 s3 相同,但由於不能改變s1, s2 的字符順序,結果爲 false.
90%的字符串問題可由動態規劃解決,本題就是一道動態規劃問題。
首先找出狀態轉移方程:
假設 dp[i][j] 代表 s1 的前 j 個字符與 s2 的前 i 個字符能否交錯組成 s3 的 前 (i + j) 個字符。
如果爲True,又代表着 s1 的第 j 個字符或者 s2 的第 i 個字符與 s3 的第(i + j)個字符相等。
接下來找出狀態轉移方程:
dp[i][j] = 1, s1.charAt(j-1) == s3.charAt(i+j-1) && dp[i][j-1] == 1;
dp[i][j] = 1, s2.charAt(i-1) == s3.charAt(i+j-1) && dp[i-1][j] == 1;
其餘情況都爲0;
即當 s1 的第 j 個字符或者 s2 的第 i 個字符與 s3 的第(i + j)個字符相等,並且 dp[i][j-1] 或者 dp[i-1][j] 等於 1.
dp[i][j-1] 或者 dp[i-1][j]等於 1 代表着能交錯組成s3 的 前 (i + j-1) 個字符。如果s1 的第 j 個字符與 s3 的第(i + j)個字符
相等,只需能交錯組成s3 的 前 (i + j-1) 個字符,由於 s1 的第 j 個字符用於組成s3 的第(i + j)個字符,所以需要dp[i][j-1] 爲 1.
同理s2 的第 i 個字符與 s3 的第(i + j)個字符相等需要dp[i-1][j] 爲 1.
爲方便理解以示例1數據填表:
s3 爲 a a d b b c b c a c
先填邊界,即單一字符串匹配情況
座標 | 0 | 1 | 2 | 3 | 4 | 5 | |
s1 | a | a | b | c | c | ||
0 | s2 | 1 | 1 | 0 | 0 | 0 | |
1 | d | 0 | |||||
2 | b | 0 | |||||
3 | b | 0 | |||||
4 | c | 0 | |||||
5 | a | 0 |
接下來先從s3的前1個字符開始,需填(0,1),(1,0),由上圖可知在邊界賦值時已完成,接下來考慮s3的前兩個字符,需填座標(2,0)(0,2)(1,1)。(2,0),(0,2)已完成,填座標(1,1):判斷 s1 第1個或者 s2 第1個是否與 s3 第2個相等。s3 第二個爲a,所以s1 第1個與之相等。接下 來還需判斷是否能組成 s3 的第 1 個字符,與(1,1)相鄰座標相加爲1的有(1,0),(0,1),由於s1第1個被用來與 s3 第2個匹配,所以只能判斷(1,0),由於dp[1][0]爲0,所以不滿足條件,dp[1][1]爲0:
座標 | 0 | 1 | 2 | 3 | 4 | 5 | |
s1 | a | a | b | c | c | ||
0 | s2 | 1 | 1 | 0 | 0 | 0 | |
1 | d | 0 | 0 | ||||
2 | b | 0 | |||||
3 | b | 0 | |||||
4 | c | 0 | |||||
5 | a | 0 |
按照轉移方程填表,從s3的子串開始:
s3前1個字符,填(1,0)(0,1)
s3前2個字符,填 (2,0)(0,2)(1,1)
s3前3個字符,填 (3,0)(0,3)(2,1)(1,2)
s3前4個字符,填 (4,0)(0,4)(1,3)(3,1)(2,2)
s3前5個字符,填 (5,0)(0,5)(2,3)(3,2)(1,4)(4,1)
s3前6個字符,填 (5,1)(1,5)(3,3)(2,4)(4,2)
s3前7個字符,填 (2,5)(5,2)(3,4)(4,3)
s3前8個字符,填 (3,5)(5,3)(4,4)
s3前9個字符,填 (4,5)(5,4)
s3前10個字符,填 (5,5)
由於s1,s2長度限制,有些組合情況沒有,如(6,0),(1,7)等,編程時需注意。
最終可得:
座標 | 0 | 1 | 2 | 3 | 4 | 5 | |
s1 | a | a | b | c | c | ||
0 | s2 | 1 | 1 | 0 | 0 | 0 | |
1 | d | 0 | 0 | 1 | 1 | 0 | 0 |
2 | b | 0 | 0 | 1 | 1 | 1 | 0 |
3 | b | 0 | 0 | 1 | 0 | 1 | 1 |
4 | c | 0 | 0 | 1 | 1 | 1 | 0 |
5 | a | 0 | 0 | 0 | 0 | 1 | 1 |
3. 示例代碼
class Solution {
public boolean isInterleave(String s1, String s2, String s3) {
int length = s3.length();
// 特殊情況處理
if(s1.isEmpty() && s2.isEmpty() && s3.isEmpty()) return true;
if(s1.isEmpty()) return s2.equals(s3);
if(s2.isEmpty()) return s1.equals(s3);
if(s1.length() + s2.length() != length) return false;
int[][] dp = new int[s2.length()+1][s1.length()+1];
// 邊界賦值
for(int i = 1;i < s1.length()+1;i++){
if(s1.substring(0,i).equals(s3.substring(0,i))){
dp[0][i] = 1;
}
}
for(int i = 1;i < s2.length()+1;i++){
if(s2.substring(0,i).equals(s3.substring(0,i))){
dp[i][0] = 1;
}
}
for(int i = 2;i <= length;i++){
// 遍歷 i 的所有組成(邊界除外)
for(int j = 1;j < i;j++){
// 防止越界
if(s1.length() >= j && i-j <= s2.length()){
if(s1.charAt(j-1) == s3.charAt(i-1) && dp[i-j][j-1] == 1){
dp[i-j][j] = 1;
}
}
// 防止越界
if(s2.length() >= j && i-j <= s1.length()){
if(s2.charAt(j-1) == s3.charAt(i-1) && dp[j-1][i-j] == 1){
dp[j][i-j] = 1;
}
}
}
}
return dp[s2.length()][s1.length()]==1;
}
}