Interleaving String
Difficulty: Medium Source: NeetCode
Problem
Given strings
s1,s2, ands3, returntrueifs3is formed by an interleaving ofs1ands2. An interleaving of two strings keeps the relative order of characters from each string but merges them together.Example 1: Input:
s1 = "aabcc",s2 = "dbbca",s3 = "aadbbcbcac"Output:trueExample 2: Input:
s1 = "aabcc",s2 = "dbbca",s3 = "aadbbbaccc"Output:falseExample 3: Input:
s1 = "",s2 = "",s3 = ""Output:trueConstraints:
0 <= s1.length, s2.length <= 100s3.length == s1.length + s2.length
Prerequisites
Before attempting this problem, you should be comfortable with:
- 2D DP on two strings — the same table structure as LCS
- Recursion with two pointers — advancing through two source strings simultaneously
1. Brute Force / Recursive
Intuition
Use two pointers i and j tracking how far we’ve consumed s1 and s2. The current character of s3 is s3[i+j]. At each step, we try matching it with s1[i] (advance i) or s2[j] (advance j). We succeed if we consume all of s1 and s2 simultaneously.
Algorithm
- Define
dfs(i, j)= cans1[:i]ands2[:j]interleave to forms3[:i+j]? - Base case:
i == len(s1)andj == len(s2)→ returnTrue. - If
i < len(s1)ands1[i] == s3[i+j], trydfs(i+1, j). - If
j < len(s2)ands2[j] == s3[i+j], trydfs(i, j+1). - Return
Trueif any path succeeds.
Solution
def isInterleave(s1, s2, s3):
if len(s1) + len(s2) != len(s3):
return False
def dfs(i, j):
if i == len(s1) and j == len(s2):
return True
if i < len(s1) and s1[i] == s3[i + j]:
if dfs(i + 1, j):
return True
if j < len(s2) and s2[j] == s3[i + j]:
if dfs(i, j + 1):
return True
return False
return dfs(0, 0)
print(isInterleave("aabcc", "dbbca", "aadbbcbcac")) # True
print(isInterleave("aabcc", "dbbca", "aadbbbaccc")) # False
print(isInterleave("", "", "")) # True
Complexity
- Time:
O(2^(m+n))— exponential without memoization - Space:
O(m+n)— recursion depth
2. Bottom-Up DP (Tabulation)
Intuition
Build a (m+1) x (n+1) boolean table where dp[i][j] is True if s1[:i] and s2[:j] can interleave to form s3[:i+j]. Start with dp[0][0] = True. Fill the first row using only s2 characters, and the first column using only s1 characters. For interior cells, it’s true if we can come from the left (match s2[j-1]) or from above (match s1[i-1]).
Algorithm
- If
len(s1) + len(s2) != len(s3), returnFalse. - Create
(m+1) x (n+1)dp table,dp[0][0] = True. - Fill first column:
dp[i][0] = dp[i-1][0] and s1[i-1] == s3[i-1]. - Fill first row:
dp[0][j] = dp[0][j-1] and s2[j-1] == s3[j-1]. - Interior:
dp[i][j] = (dp[i-1][j] and s1[i-1] == s3[i+j-1]) or (dp[i][j-1] and s2[j-1] == s3[i+j-1]). - Return
dp[m][n].
Solution
def isInterleave(s1, s2, s3):
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(1, m + 1):
dp[i][0] = dp[i - 1][0] and s1[i - 1] == s3[i - 1]
for j in range(1, n + 1):
dp[0][j] = dp[0][j - 1] and s2[j - 1] == s3[j - 1]
for i in range(1, m + 1):
for j in range(1, n + 1):
from_above = dp[i - 1][j] and s1[i - 1] == s3[i + j - 1]
from_left = dp[i][j - 1] and s2[j - 1] == s3[i + j - 1]
dp[i][j] = from_above or from_left
return dp[m][n]
print(isInterleave("aabcc", "dbbca", "aadbbcbcac")) # True
print(isInterleave("aabcc", "dbbca", "aadbbbaccc")) # False
print(isInterleave("", "", "")) # True
print(isInterleave("a", "b", "ab")) # True
print(isInterleave("a", "b", "ba")) # True
Complexity
- Time:
O(m * n) - Space:
O(m * n)— reducible toO(n)with a rolling row
Common Pitfalls
Forgetting the length check. If len(s1) + len(s2) != len(s3), it’s immediately impossible. Don’t skip this — it prevents index-out-of-bounds errors too.
Wrong index into s3. The character we’re matching in s3 at position (i, j) is s3[i+j-1] (1-indexed i, j). Getting this formula wrong is the most common bug in this problem.
Greedy won’t work. You might think “just scan left to right and match whichever string fits” — but that fails on ambiguous characters. For example if s1 = "aa" and s2 = "aa", a greedy approach can’t know which string to draw from. DP is necessary.