Given s1, s2, s3, 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.
我是用 DP 来做的。令下面的 i,j 都是从1开始。(因为这样使得 dp[0][1] 可以表示 s1的前0个字符串和s2的前1个字符串,dp[0][0]表示两者都不取。)
若 dp[i][j] = true,那么 s1的前 i 个字符(s1[0] ~ s[i - 1]) 和 s2的前 j 的字符(s1[0] ~ s2[j -1]) 可以构成s3的前 i + j 的字符(s3[0] ~ s3[i + j -1])
例如, s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac",
例如, dp[1][1], 表示s1取前1位a, s2取前1位d,是否能组成s3的前两位aa, 显然false, ad ≠ aa.
先来固定i为0,看j。
dp[0][1], 表示s1取前0位, s2取前1位d,是否能组成s3的前1位, 显然false, d ≠ a.
dp[0][2], 表示s1取前0位, s2取前2位db,是否能组成s3的前2位,
显然false, db≠ aa.
.......
再来固定j为0,看i。
dp[i][0], 表示s1取前i位, s2取前0位,是否能组成s3的前i位
那么递推表达式如何写呢?
当前新加字符(来自s1或者s2),等于s3里面对应的位( i + j 位),并且dp[i][j-1] = true或者dp[i-1][j]=true。 这两个条件都具备的情况下,才有dp[i][j] = true.
package leetcode;
public class Interleaving_String_97 {
public boolean isInterleave(String s1, String s2, String s3) {
int m = s1.length();
int n = s2.length();
if (m == 0) {
return s2.equals(s3);
}
if (n == 0) {
return s1.equals(s3);
}
if ((m + n) != s3.length()) {
return false;
}
// dp[i][j] = true,
// 表示s1的前i个字符(s1[0] ~ s[i - 1])和s2(s1[0] ~ s2[j -1])的前j的字符。
// 可以构成s3的前i + j(s3[0] ~ s3[i + j -1])的字符。
boolean dp[][] = new boolean[m + 1][n + 1];
if (s1.charAt(0) == s3.charAt(0)) {
dp[1][0] = true;
}
for (int i = 2; i <= m; i++) {
if (s1.charAt(i - 1) == s3.charAt(i - 1)) {
dp[i][0] = dp[i - 1][0];
}
}
if (s2.charAt(0) == s3.charAt(0)) {
dp[0][1] = true;
}
for (int i = 2; i <= n; i++) {
if (s2.charAt(i - 1) == s3.charAt(i - 1)) {
dp[0][i] = dp[0][i - 1];
}
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (dp[i - 1][j] == true && s1.charAt(i - 1) == s3.charAt(i + j - 1)) {
dp[i][j] = true;
}
if (dp[i][j - 1] == true && s2.charAt(j - 1) == s3.charAt(i + j - 1)) {
dp[i][j] = true;
}
}
}
return dp[m][n];
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Interleaving_String_97 i = new Interleaving_String_97();
System.out.println(i.isInterleave("a", "b", "a"));
System.out.println(i.isInterleave("aabc", "abad", "aabadabc"));
System.out.println(i.isInterleave("ab", "bc", "babc"));
}
}
这道题大神有令人眼前一亮的特殊思维。
s1, s2只有两个字符串,因此可以展平为一个二维地图,判断是否能从左上角走到右下角。
当s1到达第i个元素,s2到达第j个元素:
地图上往右一步就是 s2[j-1] 匹配 s3[i+j-1]。
地图上往下一步就是 s1[i-1] 匹配 s3[i+j-1]。
示例:s1="aa",s2="ab",s3="aaba"。标1的为可行。最终返回右下角。
0 a b
0 1 1 0
a 1 1 1
a 1 0 1
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int m = s1.size();
int n = s2.size();
if(m+n != s3.size())
return false;
vector<vector<bool> > path(m+1, vector<bool>(n+1, false));
for(int i = 0; i < m+1; i ++)
{
for(int j = 0; j < n+1; j ++)
{
if(i == 0 && j == 0)
// start
path[i][j] = true;
else if(i == 0)
path[i][j] = path[i][j-1] & (s2[j-1]==s3[j-1]);
else if(j == 0)
path[i][j] = path[i-1][j] & (s1[i-1]==s3[i-1]);
else
path[i][j] = (path[i][j-1] & (s2[j-1]==s3[i+j-1])) || (path[i-1][j] & (s1[i-1]==s3[i+j-1]));
}
}
return path[m][n];
}
};
这道题有solutions:
https://leetcode.com/problems/interleaving-string/solution/
Solution
Approach #2 Recursion with memoization [Accepted]
Algorithm
在暴力的递归方法中,我们考虑交错两个给定字符串形成的每个可能的字符串。 但是,会遇到这样的情况,其中s1 和s2的相同部分已经被处理过了,但是以不同的顺序排列(置换)。 但是无论处理的顺序如何,如果形成的结果字符串与所需的字符串(s3)匹配,最终结果仅依赖于s1 和s2的剩余部分,但不依赖于已处理的部分。 因此,递归方法导致冗余计算。
这种冗余可以通过使用记忆和递归来消除。 为此,我们保留3个指针i,j,k,它们分别对应于当前字符s1,s2,s3的索引。 此外,我们还维护一个二维记忆数组,以跟踪迄今处理的子串。 memo[i][j]存储1或0或-1,取决于字符串s1的0~ith的部分和字符串s2的0~jth的部分是否已经被计算了。之后,我们选择s1 的当前字符(被i指向),如果它等于s3的当前字符(被k指向),我们就将它包含在处理后的字符串中,并递归地调用 is_Interleave(s1, i+1, s2, j, s3, k+1,memo)
public class Solution {
public boolean is_Interleave(String s1, int i, String s2, int j, String s3, int k, int[][] memo) {
if (i == s1.length()) {
return s2.substring(j).equals(s3.substring(k));
}
if (j == s2.length()) {
return s1.substring(i).equals(s3.substring(k));
}
if (memo[i][j] >= 0) {
return memo[i][j] == 1 ? true : false;
}
boolean ans = false;
if (s3.charAt(k) == s1.charAt(i) && is_Interleave(s1, i + 1, s2, j, s3, k + 1, memo)
|| s3.charAt(k) == s2.charAt(j) && is_Interleave(s1, i, s2, j + 1, s3, k + 1, memo)) {
ans = true;
}
memo[i][j] = ans ? 1 : 0;
return ans;
}
public boolean isInterleave(String s1, String s2, String s3) {
int memo[][] = new int[s1.length()][s2.length()];
for (int i = 0; i < s1.length(); i++) {
for (int j = 0; j < s2.length(); j++) {
memo[i][j] = -1;
}
}
return is_Interleave(s1, 0, s2, 0, s3, 0, memo);
}
}
Complexity Analysis
-
Time complexity : O(2(m+n)). m is the length of s1 and n is the length of s2.
-
Space complexity :O(m+n). The size of stack for recursive calls can go upto m+n.
Approach #3 Using 2-d Dynamic Programming [Accepted]
Algorithm
public class Solution {
public boolean isInterleave(String s1, String s2, String s3) {
if (s3.length() != s1.length() + s2.length()) {
return false;
}
boolean dp[][] = new boolean[s1.length() + 1][s2.length() + 1];
for (int i = 0; i <= s1.length(); i++) {
for (int j = 0; j <= s2.length(); j++) {
if (i == 0 && j == 0) {
dp[i][j] = true;
} else if (i == 0) {
dp[i][j] = dp[i][j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1);
} else if (j == 0) {
dp[i][j] = dp[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i + j - 1);
} else {
dp[i][j] = (dp[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i + j - 1)) || (dp[i][j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1));
}
}
}
return dp[s1.length()][s2.length()];
}
}
Complexity Analysis
-
Time complexity : O(m∗n). dp array of size m∗n is filled.
-
Space complexity : O(m∗n). 2-d DP of size (m+1)∗(n+1) is required. m and n are the lengths of strings s1 and s2 repectively.
Approach #4 Using 1-d Dynamic Programming [Accepted]:
Algorithm
This approach is the same as the previous approach except that we have used only 1-d dp array to store the results of the prefixes of the strings processed so far. Instead of maintaining a 2-d array, we can maintain a 1-d array only and update the array's element dp[i] when needed using dp[i−1] and the previous value of dp[i].
public class Solution {
public boolean isInterleave(String s1, String s2, String s3) {
if (s3.length() != s1.length() + s2.length()) {
return false;
}
boolean dp[] = new boolean[s2.length() + 1];
for (int i = 0; i <= s1.length(); i++) {
for (int j = 0; j <= s2.length(); j++) {
if (i == 0 && j == 0) {
dp[j] = true;
} else if (i == 0) {
dp[j] = dp[j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1);
} else if (j == 0) {
dp[j] = dp[j] && s1.charAt(i - 1) == s3.charAt(i + j - 1);
} else {
dp[j] = (dp[j] && s1.charAt(i - 1) == s3.charAt(i + j - 1)) || (dp[j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1));
}
}
}
return dp[s2.length()];
}
}
(说实话这个一维dp方法我不是太看得懂啊。。。)
Complexity Analysis
-
Time complexity : O(m∗n). dp array of size n is filled m times.
-
Space complexity : O(n). n is the length of the string s1.