给定三个字符串 s1, s2, s3, 验证 s3 是否是由 s1 和 s2 交错组成的。
示例 1:
示例 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 |
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;
}
}