Given three strings s1
, s2
, and s3
, determine if s3
is formed by an interleaving of s1
and s2
. An interleaving of two strings s1
and s2
is a string that contains all the characters of s1
and s2
exactly once, keeping the order of characters from each string.
s1
or s2
.s1
or s2
(if available), and append it to the result.s1
and s2
must be used exactly once to form s3
.true
if s3
is a valid interleaving, false
otherwise.
Example:
s1 = "abc"
, s2 = "def"
, s3 = "adbcef"
→ true
To solve this problem, we first recognize that we need to check if we can build s3
by choosing characters from s1
and s2
in their respective orders. A brute-force solution would try all possible ways to interleave s1
and s2
, but this would be very slow for longer strings.
We realize that at each position in s3
, we have two choices: take the next character from s1
(if it matches), or from s2
(if it matches). This is similar to exploring a binary tree of decisions. However, many subproblems will be repeated, so we can use dynamic programming (DP) or memoization to avoid redundant computations.
The key insight is that the problem can be broken down into checking if prefixes of s1
and s2
can form a prefix of s3
.
We use dynamic programming to efficiently solve the interleaving string problem. Here’s a step-by-step explanation:
len(s1) + len(s2) != len(s3)
, return false immediately, because we can't use all characters exactly once.
dp
where dp[i][j]
is true if s3[0..i+j-1]
can be formed by interleaving s1[0..i-1]
and s2[0..j-1]
.
dp[0][0] = True
(empty strings interleave to form an empty string).
i
(from 0 to len(s1)
) and j
(from 0 to len(s2)
):i > 0
and s1[i-1] == s3[i+j-1]
, set dp[i][j] |= dp[i-1][j]
.j > 0
and s2[j-1] == s3[i+j-1]
, set dp[i][j] |= dp[i][j-1]
.dp[len(s1)][len(s2)]
tells us whether s3
can be formed by interleaving s1
and s2
.
We use a DP table because it lets us efficiently cache the results of subproblems, avoiding redundant calculations and ensuring each state is only computed once.
Let's take s1 = "abc"
, s2 = "def"
, s3 = "adbcef"
.
3 + 3 = 6
matches len(s3)
.
dp[0][0] = True
(empty strings).dp[1][0] = True
if s1[0] == s3[0]
('a' == 'a').dp[1][1]
: Can we use s1[0]
and s2[0]
to form s3[0:2]
("ad")?s1
or s2
matches the next character in s3
, and update dp
accordingly.dp[3][3]
will be True
, as we can interleave "abc" and "def" to get "adbcef".
Brute-force: Without memoization, the number of ways to interleave is exponential, specifically O(2^{m+n})
where m
and n
are the lengths of s1
and s2
.
Optimized (DP):
O(m \times n)
because we fill a table with m+1
rows and n+1
columns, and each cell is computed in constant time.O(m \times n)
for the DP table. This can be reduced to O(n)
using a rolling array, since only the previous row is needed at each step.
The Interleaving String problem is a classic example of using dynamic programming to efficiently solve a problem that would otherwise require exponential time. By breaking the problem into subproblems and caching results, we avoid redundant work. The DP approach is intuitive: at each step, we check if the current character in s3
can be matched by advancing in s1
or s2
, and build up the solution from the smallest prefixes. This method is efficient and robust, making it suitable for large inputs.
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
if len(s1) + len(s2) != len(s3):
return False
dp = [[False] * (len(s2) + 1) for _ in range(len(s1) + 1)]
dp[0][0] = True
for i in range(len(s1) + 1):
for j in range(len(s2) + 1):
if i > 0 and s1[i - 1] == s3[i + j - 1]:
dp[i][j] = dp[i][j] or dp[i - 1][j]
if j > 0 and s2[j - 1] == s3[i + j - 1]:
dp[i][j] = dp[i][j] or dp[i][j - 1]
return dp[len(s1)][len(s2)]
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
if (s1.length() + s2.length() != s3.length()) return false;
int m = s1.length(), n = s2.length();
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) {
if (i > 0 && s1[i - 1] == s3[i + j - 1])
dp[i][j] = dp[i][j] || dp[i - 1][j];
if (j > 0 && s2[j - 1] == s3[i + j - 1])
dp[i][j] = dp[i][j] || dp[i][j - 1];
}
}
return dp[m][n];
}
};
class Solution {
public boolean isInterleave(String s1, String s2, String s3) {
if (s1.length() + s2.length() != s3.length()) return false;
int m = s1.length(), n = s2.length();
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++) {
if (i > 0 && s1.charAt(i - 1) == s3.charAt(i + j - 1))
dp[i][j] = dp[i][j] || dp[i - 1][j];
if (j > 0 && s2.charAt(j - 1) == s3.charAt(i + j - 1))
dp[i][j] = dp[i][j] || dp[i][j - 1];
}
}
return dp[m][n];
}
}
var isInterleave = function(s1, s2, s3) {
if (s1.length + s2.length !== s3.length) return false;
const m = s1.length, n = s2.length;
const dp = Array.from({length: m + 1}, () => Array(n + 1).fill(false));
dp[0][0] = true;
for (let i = 0; i <= m; i++) {
for (let j = 0; j <= n; j++) {
if (i > 0 && s1[i - 1] === s3[i + j - 1])
dp[i][j] = dp[i][j] || dp[i - 1][j];
if (j > 0 && s2[j - 1] === s3[i + j - 1])
dp[i][j] = dp[i][j] || dp[i][j - 1];
}
}
return dp[m][n];
};