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 of s and t is at most 1.s = s1 + s2 + ... + snt = t1 + t2 + ... + tms and t is s1 + t1 + s2 + t2 + ... or t1 + 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
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.
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?
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.
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.
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.
Before attempting this problem, you should be comfortable with:
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:
s1s2Using 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.
dfs(i, j, k):i is the current index in s1j is the current index in s2k is the current index in s3k reaches the end of s3:true only if both s1 and s2 are also fully useds1 matches s3[k]:s1true, stop and return trues2 matches s3[k]:s2true, stop and return truefalse(0, 0, 0)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)Where is the length of the string and is the length of the string .
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:
i in s1j in s2The recursive function answers:
“Can we form the rest of s3 using s1[i:] and s2[j:]?”
s1 and s2 add up to the length of s3:false immediatelydp where:(i, j)s3[k:] can be formed from s1[i:] and s2[j:]dfs(i, j, k):i is the current index in s1j is the current index in s2k is the current index in s3k reaches the end of s3:true only if both s1 and s2 are fully used(i, j) is already in dp:s1 if it matches s3[k]s2 if it matches s3[k]dp[(i, j)](0, 0, 0)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)Where is the length of the string and is the length of the string .
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:
s1[i:]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.
s1 and s2 add up to the length of s3:falsedp of size (len(s1) + 1) x (len(s2) + 1):dp[i][j] is true if s3[i + j:] can be formed using s1[i:] and s2[j:]dp[len(s1)][len(s2)] = true because empty strings can form an empty string(i, j):s1 matches s3[i + j] and dp[i + 1][j] is true, then set dp[i][j] = trues2 matches s3[i + j] and dp[i][j + 1] is true, then set dp[i][j] = truedp[0][0]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]Where is the length of the string and is the length of the string .
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:
i + 1) andSo 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.
m = len(s1) and n = len(s2). If m + n != len(s3), return false.s2 is shorter than s1, swap them so that s2 is always the longer string.dp of size n + 1:dp[j] will represent the DP values from the "next row" (i.e., for i + 1)truei from m down to 0:nextDp for the current rowi == m, set nextDp[n] = true (empty suffixes match)j from n down to 0:s1 (matches s3[i + j]) and dp[j] is true, set nextDp[j] = trues2 (matches s3[i + j]) and nextDp[j + 1] is true, set nextDp[j] = truedp = nextDpdp[0], meaning we can form s3 starting from the beginning of both stringsclass 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]Where is the length of the string and is the length of the string .
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:
(i, j), we have used i characters from s1 and j characters from s2s3 is at index i + jFrom this state, we can move forward in two ways:
s1[i] if it matches s3[i + j]s2[j] if it matches s3[i + j]The 2D DP solution stores this for every (i, j), but we can do better:
next array, we can update the 1D array in-place using one extra variable that tracks the “right neighbor” valueWe also swap strings so that s2 is the longer one, keeping the DP array as small as possible.
m = len(s1) and n = len(s2).m + n != len(s3), return false immediately.s2 is shorter than s1, swap them so the DP array size becomes O(min(m, n)).dp of size n + 1:dp[j] represents whether s3[i + j:] can be formed using s1[i:] and s2[j:] for the current idp[n] = true (when both suffixes are empty)i from m down to 0:nextDp that represents the value to the right (dp[j + 1]) for the current rowtrue only when i == m (bottom row base case)j from n down to 0:(i, j) is valid:s1 matches and the state below (dp[j]) was valids2 matches and the state to the right (nextDp) is validdp[j] (in-place update)nextDp to the new dp[j] for the next iteration to the leftdp[0] tells whether s3 can be formed starting from the beginning of both strings.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]Where is the length of the string and is the length of the string .
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.
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.
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.