116. Interleaving String
View on NeetCode
View on LeetCode
Problem
Given strings s1, s2, and s3, find whether s3 is formed by an interleaving of s1 and s2.
An interleaving of two strings s and t is a configuration where s and t are divided into n and m substrings respectively, such that:
s = s1 + s2 + ... + snt = t1 + t2 + ... + tm|n - m| <= 1- The interleaving is
s1 + t1 + s2 + t2 + s3 + t3 + ...ort1 + s1 + t2 + s2 + t3 + s3 + ...
Note: a + b is the concatenation of strings a and b.
Example 1:
Input: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
Output: true
Explanation: One way to obtain s3 is:
Split s1 into s1 = "aa" + "bc" + "c", and s2 into s2 = "dbbc" + "a".
Interleaving the two splits, we get "aa" + "dbbc" + "bc" + "a" + "c" = "aadbbcbcac".
Example 2:
Input: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
Output: false
Example 3:
Input: s1 = "", s2 = "", s3 = ""
Output: true
Constraints:
0 <= s1.length, s2.length <= 1000 <= s3.length <= 200s1,s2, ands3consist of lowercase English letters.
Solution
Approach: 2D Dynamic Programming
The key insight is that at each position in s3, we check if we can form it by taking a character from s1 or s2, given what we’ve used so far.
Implementation
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
m, n, l = len(s1), len(s2), len(s3)
# Length check
if m + n != l:
return False
# dp[i][j] = can we form s3[0:i+j] using s1[0:i] and s2[0:j]
dp = [[False] * (n + 1) for _ in range(m + 1)]
dp[0][0] = True
# Fill first column (only using s1)
for i in range(1, m + 1):
dp[i][0] = dp[i-1][0] and s1[i-1] == s3[i-1]
# Fill first row (only using s2)
for j in range(1, n + 1):
dp[0][j] = dp[0][j-1] and s2[j-1] == s3[j-1]
# Fill rest of table
for i in range(1, m + 1):
for j in range(1, n + 1):
k = i + j - 1 # Current position in s3
# Can we match by taking from s1?
if s1[i-1] == s3[k]:
dp[i][j] = dp[i][j] or dp[i-1][j]
# Can we match by taking from s2?
if s2[j-1] == s3[k]:
dp[i][j] = dp[i][j] or dp[i][j-1]
return dp[m][n]
Approach 2: Space-Optimized 1D DP
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
m, n, l = len(s1), len(s2), len(s3)
if m + n != l:
return False
dp = [False] * (n + 1)
for i in range(m + 1):
for j in range(n + 1):
if i == 0 and j == 0:
dp[j] = True
elif i == 0:
dp[j] = dp[j-1] and s2[j-1] == s3[j-1]
elif j == 0:
dp[j] = dp[j] and s1[i-1] == s3[i-1]
else:
k = i + j - 1
dp[j] = (dp[j] and s1[i-1] == s3[k]) or \
(dp[j-1] and s2[j-1] == s3[k])
return dp[n]
Approach 3: Recursion with Memoization
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
if len(s1) + len(s2) != len(s3):
return False
memo = {}
def dp(i, j):
k = i + j
if k == len(s3):
return True
if (i, j) in memo:
return memo[(i, j)]
result = False
# Try taking from s1
if i < len(s1) and s1[i] == s3[k]:
result = result or dp(i + 1, j)
# Try taking from s2
if j < len(s2) and s2[j] == s3[k]:
result = result or dp(i, j + 1)
memo[(i, j)] = result
return result
return dp(0, 0)
Complexity Analysis
2D DP:
- Time Complexity: O(m * n), where m and n are lengths of s1 and s2.
- Space Complexity: O(m * n) for the dp table.
1D DP:
- Time Complexity: O(m * n).
- Space Complexity: O(n), only one row at a time.
Key Insights
-
Length Constraint: s3 length must equal s1 length + s2 length.
-
DP State:
dp[i][j]= can we form s3[0:i+j] using first i chars of s1 and first j chars of s2. -
Two Choices: At each position in s3, we can take the next character from s1 or s2 (if it matches).
-
Position Mapping: Position k in s3 corresponds to using i chars from s1 and j chars from s2, where k = i + j - 1.
- Base Cases:
- dp[0][0] = True (empty strings interleave to empty)
- First row: can only use s2
- First column: can only use s1
- Order Preservation: Characters from s1 and s2 must maintain their relative order in s3.