97. Interleaving String - Explanation
Description
You are given three strings s1, s2, and s3. Return true if s3 is formed by interleaving s1 and s2 together or false otherwise.
Interleaving two strings s and t is done by dividing s and t into n and m substrings respectively, where the following conditions are met
|n - m| <= 1, i.e. the difference between the number of substrings ofsandtis at most1.s = s1 + s2 + ... + snt = t1 + t2 + ... + tm- Interleaving
sandtiss1 + t1 + s2 + t2 + ...ort1 + s1 + t2 + s2 + ...
You may assume that s1, s2 and s3 consist of lowercase English letters.
Example 1:
Input: s1 = "aaaa", s2 = "bbbb", s3 = "aabbbbaa"
Output: trueExplanation: We can split s1 into ["aa", "aa"], s2 can remain as "bbbb" and s3 is formed by interleaving ["aa", "aa"] and "bbbb".
Example 2:
Input: s1 = "", s2 = "", s3 = ""
Output: trueExample 3:
Input: s1 = "abc", s2 = "xyz", s3 = "abxzcy"
Output: falseExplanation: We can't split s3 into ["ab", "xz", "cy"] as the order of characters is not maintained.
Constraints:
0 <= s1.length, s2.length <= 1000 <= s3.length <= 200
Topics
Recommended Time & Space Complexity
You should aim for a solution as good or better than O(m * n) time and O(m * n) space, where m is the length of the string s1 and n is the length of the string s2.
Hint 1
If the sum of the characters in s1 and s2 does not equal s3, we return false. Think in terms of recursion and visualize it as a decision tree, where we explore different combinations of portions from both strings. Can you determine the possible decisions at each recursion step?
Hint 2
We recursively iterate through the strings using indices i, j, and k for s1, s2, and s3, respectively. At each step, we extend the current path in two directions based on whether the k-th character of s3 matches the current character of s1 or s2. If any path returns true, we immediately return true. If k goes out of bounds, it means we have successfully formed the interleaved string, so we return true.
Hint 3
This approach is exponential. Can you think of a way to optimize it? Since k depends on i and j, it can be treated as a constant, as we can derive k using i + j.
Hint 4
We can use memoization to cache the results of recursive calls and avoid redundant computations. Treating i and j as states, we can use a hash map or a 2D array to store the results.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Recursion - Understanding how to break down string problems into smaller subproblems
- Dynamic Programming (Memoization) - Caching recursive results to avoid redundant computation
- 2D Dynamic Programming - Building solutions using a 2D table where states depend on two string indices
- String Manipulation - Working with string indices and character comparisons
1. Recursion
Intuition
This problem asks whether the string s3 can be formed by interleaving characters from s1 and s2, while keeping the relative order of characters from each string.
At any position in s3, we have at most two choices:
- take the next character from
s1 - take the next character from
s2
Using recursion, we try all valid ways of building s3 character by character.
The recursive function represents:
“Can we form s3 starting from index k, using characters from s1 starting at i and s2 starting at j?”
If we successfully consume all characters of s3 and also reach the end of both s1 and s2, then s3 is a valid interleaving.
Algorithm
- Define a recursive function
dfs(i, j, k):iis the current index ins1jis the current index ins2kis the current index ins3
- If
kreaches the end ofs3:- Return
trueonly if boths1ands2are also fully used
- Return
- If the next character of
s1matchess3[k]:- Recurse by taking the character from
s1 - If it returns
true, stop and returntrue
- Recurse by taking the character from
- If the next character of
s2matchess3[k]:- Recurse by taking the character from
s2 - If it returns
true, stop and returntrue
- Recurse by taking the character from
- If neither choice works:
- Return
false
- Return
- Start the recursion from indices
(0, 0, 0) - Return the final result
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
def dfs(i, j, k):
if k == len(s3):
return (i == len(s1)) and (j == len(s2))
if i < len(s1) and s1[i] == s3[k]:
if dfs(i + 1, j, k + 1):
return True
if j < len(s2) and s2[j] == s3[k]:
if dfs(i, j + 1, k + 1):
return True
return False
return dfs(0, 0, 0)Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the string and is the length of the string .
2. Dynamic Programming (Top-Down)
Intuition
This problem asks whether the string s3 can be formed by interleaving characters from s1 and s2 while preserving the relative order of characters in both strings.
The recursive approach explores all possible interleavings, but many states repeat. To make it efficient, we use top-down dynamic programming (memoization).
A key observation is that the position in s3 is always determined by how many characters we have already taken from s1 and s2.
So the state can be defined using just:
- index
iins1 - index
jins2
The recursive function answers:
“Can we form the rest of s3 using s1[i:] and s2[j:]?”
Algorithm
- First, check if the lengths of
s1ands2add up to the length ofs3:- If not, return
falseimmediately
- If not, return
- Create a memoization map
dpwhere:- the key is
(i, j) - the value is whether
s3[k:]can be formed froms1[i:]ands2[j:]
- the key is
- Define a recursive function
dfs(i, j, k):iis the current index ins1jis the current index ins2kis the current index ins3
- If
kreaches the end ofs3:- Return
trueonly if boths1ands2are fully used
- Return
- If the state
(i, j)is already indp:- Return the stored result
- Try taking the next character from
s1if it matchess3[k] - If that does not work, try taking the next character from
s2if it matchess3[k] - Store the result in
dp[(i, j)] - Start the recursion from
(0, 0, 0) - Return the final result
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
if len(s1) + len(s2) != len(s3):
return False
dp = {}
def dfs(i, j, k):
if k == len(s3):
return (i == len(s1)) and (j == len(s2))
if (i, j) in dp:
return dp[(i, j)]
res = False
if i < len(s1) and s1[i] == s3[k]:
res = dfs(i + 1, j, k + 1)
if not res and j < len(s2) and s2[j] == s3[k]:
res = dfs(i, j + 1, k + 1)
dp[(i, j)] = res
return res
return dfs(0, 0, 0)Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the string and is the length of the string .
3. Dynamic Programming (Bottom-Up)
Intuition
We need to check whether the string s3 can be formed by interleaving s1 and s2, while keeping the relative order of characters from both strings.
Instead of recursion, we can solve this using bottom-up dynamic programming.
The idea is to determine, for every possible pair of positions (i, j), whether it is possible to form the suffix of s3 starting at position i + j using:
- the substring
s1[i:] - the substring
s2[j:]
If either taking the next character from s1 or from s2 leads to a valid state, then the current state is also valid.
Algorithm
- First, check if the lengths of
s1ands2add up to the length ofs3:- If not, return
false
- If not, return
- Create a 2D DP table
dpof size(len(s1) + 1) x (len(s2) + 1):dp[i][j]istrueifs3[i + j:]can be formed usings1[i:]ands2[j:]
- Initialize the base case:
dp[len(s1)][len(s2)] = truebecause empty strings can form an empty string
- Fill the table in reverse order (from bottom-right to top-left):
- For each position
(i, j):- If the next character of
s1matchess3[i + j]anddp[i + 1][j]istrue, then setdp[i][j] = true - If the next character of
s2matchess3[i + j]anddp[i][j + 1]istrue, then setdp[i][j] = true
- If the next character of
- After filling the table, the answer is stored in
dp[0][0] - Return
dp[0][0]
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 i in range(len(s1) + 1)]
dp[len(s1)][len(s2)] = True
for i in range(len(s1), -1, -1):
for j in range(len(s2), -1, -1):
if i < len(s1) and s1[i] == s3[i + j] and dp[i + 1][j]:
dp[i][j] = True
if j < len(s2) and s2[j] == s3[i + j] and dp[i][j + 1]:
dp[i][j] = True
return dp[0][0]Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the string and is the length of the string .
4. Dynamic Programming (Space Optimized)
Intuition
We want to know if s3 can be built by interleaving s1 and s2 while keeping the order of characters from each string.
In the 2D DP solution, we used a table dp[i][j] to represent whether s3[i + j:] can be formed using s1[i:] and s2[j:].
But notice something important: to compute row i, we only need information from:
- the row below (
i + 1) and - the current row as we move across columns
So we do not need the full 2D table. We can compress it and keep only one row at a time, which reduces memory usage.
To make this even more efficient, we ensure that s2 is the longer string so the 1D array stays as small as possible.
Algorithm
- Let
m = len(s1)andn = len(s2). Ifm + n != len(s3), returnfalse. - If
s2is shorter thans1, swap them so thats2is always the longer string. - Create a 1D boolean array
dpof sizen + 1:dp[j]will represent the DP values from the "next row" (i.e., fori + 1)
- Initialize the base case where both strings are fully used:
- set the last position to
true
- set the last position to
- Iterate
ifrommdown to0:- Create a new array
nextDpfor the current row - If
i == m, setnextDp[n] = true(empty suffixes match)
- Create a new array
- Iterate
jfromndown to0:- If we can take the next character from
s1(matchess3[i + j]) anddp[j]istrue, setnextDp[j] = true - If we can take the next character from
s2(matchess3[i + j]) andnextDp[j + 1]istrue, setnextDp[j] = true
- If we can take the next character from
- After finishing the row, assign
dp = nextDp - The final answer will be
dp[0], meaning we can forms3starting from the beginning of both strings
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
if n < m:
s1, s2 = s2, s1
m, n = n, m
dp = [False for _ in range(n + 1)]
dp[n] = True
for i in range(m, -1, -1):
nextDp = [False for _ in range(n + 1)]
if i == m:
nextDp[n] = True
for j in range(n, -1, -1):
if i < m and s1[i] == s3[i + j] and dp[j]:
nextDp[j] = True
if j < n and s2[j] == s3[i + j] and nextDp[j + 1]:
nextDp[j] = True
dp = nextDp
return dp[0]Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the string and is the length of the string .
5. Dynamic Programming (Optimal)
Intuition
We want to check if s3 can be formed by interleaving s1 and s2 while keeping the order of characters from both strings.
A common DP idea is:
- at positions
(i, j), we have usedicharacters froms1andjcharacters froms2 - so the next character we must match in
s3is at indexi + j
From this state, we can move forward in two ways:
- take
s1[i]if it matchess3[i + j] - take
s2[j]if it matchess3[i + j]
The 2D DP solution stores this for every (i, j), but we can do better:
- each DP row only depends on the row below and the current row being built
- so we can reuse a single 1D array
- and instead of building a separate
nextarray, we can update the 1D array in-place using one extra variable that tracks the “right neighbor” value
We also swap strings so that s2 is the longer one, keeping the DP array as small as possible.
Algorithm
- Let
m = len(s1)andn = len(s2). - If
m + n != len(s3), returnfalseimmediately. - If
s2is shorter thans1, swap them so the DP array size becomesO(min(m, n)). - Create a boolean array
dpof sizen + 1:dp[j]represents whethers3[i + j:]can be formed usings1[i:]ands2[j:]for the currenti
- Initialize the base case:
- set
dp[n] = true(when both suffixes are empty)
- set
- Iterate
ifrommdown to0:- keep a variable
nextDpthat represents the value to the right (dp[j + 1]) for the current row - initialize it as
trueonly wheni == m(bottom row base case)
- keep a variable
- Iterate
jfromndown to0:- compute whether the state
(i, j)is valid:- it is valid if taking from
s1matches and the state below (dp[j]) was valid - or taking from
s2matches and the state to the right (nextDp) is valid
- it is valid if taking from
- write the result back into
dp[j](in-place update) - update
nextDpto the newdp[j]for the next iteration to the left
- compute whether the state
- After all updates,
dp[0]tells whethers3can be formed starting from the beginning of both strings. - Return
dp[0]
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
if n < m:
s1, s2 = s2, s1
m, n = n, m
dp = [False for _ in range(n + 1)]
dp[n] = True
for i in range(m, -1, -1):
nextDp = True if i == m else False
for j in range(n, -1, -1):
res = False if j < n else nextDp
if i < m and s1[i] == s3[i + j] and dp[j]:
res = True
if j < n and s2[j] == s3[i + j] and nextDp:
res = True
dp[j] = res
nextDp = dp[j]
return dp[0]Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the string and is the length of the string .
Common Pitfalls
Skipping the Length Check
A quick optimization that many solutions miss is checking if len(s1) + len(s2) == len(s3) upfront. If the lengths do not match, interleaving is impossible regardless of the characters. Skipping this check leads to unnecessary computation and can cause index out-of-bounds errors in some implementations.
Confusing Position Tracking with Three Indices
Since k = i + j always holds (where k is position in s3, i in s1, j in s2), you only need to track two indices. Some implementations incorrectly track all three independently, leading to inconsistent states or missed memoization opportunities. The key insight is that knowing positions in s1 and s2 uniquely determines the position in s3.
Greedy Character Matching
When both s1[i] and s2[j] match s3[k], you cannot greedily choose one over the other. Both branches must be explored. A common bug is to always prefer taking from s1 (or s2) when both match, which fails for cases like s1 = "a", s2 = "a", s3 = "aa" where either order works but a greedy approach might get stuck.
Sign in to join the discussion