97. 交错字符串
题目描述
输入三个字符串 s1、s2、s3,判断 s3 能否由 s1 和 s2 交错拼成。能则返回 true,否则返回 false。
交错的意思:把 s1 切成若干非空小段,s2 也切成若干非空小段,两边段数相差不超过 1。然后从其中一方开始,轮流取一段接在后面。
等价地说,s3 是 s1 与 s2 的所有字符穿插合并而成,并且来自 s1 的字符保持它们在 s1 中的顺序,来自 s2 的也一样。
示例 1
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac" 输出:true
解释:s1 切成 aa|bc|c,s2 切成 dbbc|a,轮流拼接得 aa+dbbc+bc+a+c,正好是 s3。
示例 2
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc" 输出:false
解释:无论怎么切分都拼不出 s3。
示例 3
输入:s1 = "", s2 = "", s3 = "" 输出:true
解释:三个都是空串,视为可以拼成。
数据范围
0 <= s1.length, s2.length <= 1000 <= s3.length <= 200s1、s2、s3只含小写英文字母。
进阶:能否只用 O(s2.length) 的额外空间?
解题思路
设 dp[i][j] 表示 s1 前 i 个字符和 s2 前 j 个字符能否交错组成 s3 前 i+j 个字符。s3 的第 i+j 个字符 s3[i+j-1] 要么来自 s1[i-1](则需 dp[i-1][j] 为真),要么来自 s2[j-1](则需 dp[i][j-1] 为真)。长度不等时直接返回 false。
为什么正确
按行从左到右填表,上方和左边的格都已算好。
复杂度分析
时间 O(mn),空间 O(mn)。
易错点
贪心地“能从 s1 取就从 s1 取”是错的:s1 和 s2 当前字符相同时,两条路都要试,所以需要 DP。不要忘记先检查长度。
参考代码
Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
m, n = len(s1), len(s2)
if m + n != len(s3):
return False
dp = [[False] * (n + 1) for _ in range(m + 1)]
dp[0][0] = True
for i in range(m + 1):
for j in range(n + 1):
k = i + j - 1
if i > 0 and s1[i - 1] == s3[k] and dp[i - 1][j]:
dp[i][j] = True
if j > 0 and s2[j - 1] == s3[k] and dp[i][j - 1]:
dp[i][j] = True
return dp[m][n]class Solution {
public boolean isInterleave(String s1, String s2, String s3) {
int m = s1.length(), n = s2.length();
if (m + n != s3.length()) {
return false;
}
boolean[][] dp = new boolean[m + 1][n + 1];
dp[0][0] = true;
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
int k = i + j - 1;
if (i > 0 && s1.charAt(i - 1) == s3.charAt(k) && dp[i - 1][j]) {
dp[i][j] = true;
}
if (j > 0 && s2.charAt(j - 1) == s3.charAt(k) && dp[i][j - 1]) {
dp[i][j] = true;
}
}
}
return dp[m][n];
}
}class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int m = s1.size(), n = s2.size();
if (m + n != (int)s3.size()) {
return false;
}
vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
dp[0][0] = true;
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
int k = i + j - 1;
if (i > 0 && s1[i - 1] == s3[k] && dp[i - 1][j]) {
dp[i][j] = true;
}
if (j > 0 && s2[j - 1] == s3[k] && dp[i][j - 1]) {
dp[i][j] = true;
}
}
}
return dp[m][n];
}
};func isInterleave(s1 string, s2 string, s3 string) bool {
m, n := len(s1), len(s2)
if m+n != len(s3) {
return false
}
dp := make([][]bool, m+1)
for i := range dp {
dp[i] = make([]bool, n+1)
}
dp[0][0] = true
for i := 0; i <= m; i++ {
for j := 0; j <= n; j++ {
k := i + j - 1
if i > 0 && s1[i-1] == s3[k] && dp[i-1][j] {
dp[i][j] = true
}
if j > 0 && s2[j-1] == s3[k] && dp[i][j-1] {
dp[i][j] = true
}
}
}
return dp[m][n]
}var isInterleave = function(s1, s2, s3) {
const m = s1.length, n = s2.length;
if (m + n !== s3.length) {
return false;
}
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(false));
dp[0][0] = true;
for (let i = 0; i <= m; i++) {
for (let j = 0; j <= n; j++) {
const k = i + j - 1;
if (i > 0 && s1[i - 1] === s3[k] && dp[i - 1][j]) {
dp[i][j] = true;
}
if (j > 0 && s2[j - 1] === s3[k] && dp[i][j - 1]) {
dp[i][j] = true;
}
}
}
return dp[m][n];
};function isInterleave(s1: string, s2: string, s3: string): boolean {
const m = s1.length, n = s2.length;
if (m + n !== s3.length) {
return false;
}
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(false));
dp[0][0] = true;
for (let i = 0; i <= m; i++) {
for (let j = 0; j <= n; j++) {
const k = i + j - 1;
if (i > 0 && s1[i - 1] === s3[k] && dp[i - 1][j]) {
dp[i][j] = true;
}
if (j > 0 && s2[j - 1] === s3[k] && dp[i][j - 1]) {
dp[i][j] = true;
}
}
}
return dp[m][n];
}