DSAPrep
Medium2-D DP

Interleaving String

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 they are divided into n and m substrings respectively, such that s = s1 + s2 + ... + sn, t = t1 + t2 + ... + tm, |n - m| <= 1, and the interleaving is s1 + t1 + s2 + t2 + ... or t1 + s1 + t2 + s2 + ....

Example 1

Input: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
Output: true
Explanation: One way: split s1 into "aa"+"bc"+"c" and s2 into "dbbc"+"a", then interleave as "aa"+"dbbc"+"bc"+"a"+"c" = "aadbbcbcac".

Example 2

Input: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
Output: false
Explanation: It is impossible to interleave s2 with any other string to obtain s3.

Example 3

Input: s1 = "", s2 = "", s3 = ""
Output: true

Constraints

  • 0 <= s1.length, s2.length <= 100
  • 0 <= s3.length <= 200
  • s1, s2, and s3 consist of lowercase English letters.
Follow-up: Could you solve it using only O(s2.length) additional memory space?
View original on LeetCode ↗

If s3 is an interleaving of s1 and s2, then having consumed i characters of s1 and j characters of s2 must together account for exactly the first i + j characters of s3. Whether the state (i, j) is reachable does not depend on which order the characters were taken in, only on whether the next needed character of s3 can come from s1 or s2 at this point — a classic 2-D boolean DP.

Brute Force Recursion

Time O(2^(m+n))Space O(m+n)

At each pair of pointers (i, j) into s1 and s2, try consuming the next character from whichever string’s next character matches s3[i+j], and recurse.

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
def rec(i: int, j: int) -> bool:
if i == m and j == n:
return True
k = i + j
take_s1 = i < m and s1[i] == s3[k] and rec(i + 1, j)
take_s2 = j < n and s2[j] == s3[k] and rec(i, j + 1)
return take_s1 or take_s2
return rec(0, 0)

Each call can branch two ways, and the same (i, j) pair is reached along many different orderings of characters — exponential blow-up.

2-D DP Table (Bottom-Up)

OptimalTime O(m·n)Space O(m·n)

dp[i][j] is true if the first i + j characters of s3 can be formed by interleaving s1[0:i] and s2[0:j]. Base case dp[0][0] = True (two empty prefixes trivially interleave into an empty string). Row 0 and column 0 extend that by matching a single string against a prefix of s3 directly. Every other cell is reachable if either the last character came from s1 (dp[i-1][j] was reachable and s1[i-1] == s3[i+j-1]) or from s2 (symmetric).

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
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):
k = i + j - 1
dp[i][j] = (dp[i - 1][j] and s1[i - 1] == s3[k]) or \
(dp[i][j - 1] and s2[j - 1] == s3[k])
return dp[m][n]

Filling the table for s1 = "ab", s2 = "ba", s3 = "abba" (a small illustrative case, true; 1 = true, 0 = false):

1
·
·
·
·
·
·
·
·
1 / 8
comparingseenresultdiscarded

Base case: two empty prefixes interleave into an empty string, so dp[0][0] is true.

Complexity: each of the (m+1)·(n+1) cells does O(1) work → O(m·n) time, O(m·n) space.

1-D Rolling Array

Time O(m·n)Space O(n)

Row i of the table only depends on row i-1 (via dp[i-1][j]) and on itself at the previous column (via dp[i][j-1]). A single 1-D array reused across rows captures both, exactly like the unique-paths rolling-array trick, as long as the update order is preserved.

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
dp = [False] * (n + 1)
dp[0] = True
for j in range(1, n + 1):
dp[j] = dp[j - 1] and s2[j - 1] == s3[j - 1]
for i in range(1, m + 1):
dp[0] = dp[0] and s1[i - 1] == s3[i - 1]
for j in range(1, n + 1):
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]

Same O(m·n) time, but only one row of length n + 1 is kept — O(n) space, matching the follow-up’s target.