Interleaving String

Interleaving String sounds easy until you try it. You take two strings and mix them together. The question is whether a third string is a valid mix. The trap is that a greedy left to right match fails. The interviewer wants to see if you can model this with a clean two-dimensional grid.

🎯 The Problem

You get three strings, s1, s2, and s3. Decide if s3 is formed by mixing s1 and s2.

The rules:

  • Mixing means you keep the order of letters inside each string.
  • You only interleave them. You never reorder letters within s1 or within s2.
  • The length of s3 must equal the length of s1 plus the length of s2, or the answer is false right away.
  • Return true or false.

For s1 = "aab", s2 = "axy", and s3 = "aaxaby", you can build s3 by taking letters in order from both. So the answer is true.

Input: s1 = "aab", s2 = "axy", s3 = "aaxaby"
Output: true
Explanation: take a(s1) a(s1) x(s2) a... wait, build it as:
a(s1) a(s2? no) ...
One valid mix: a a x a b y
s1 gives: a a b (in order)
s2 gives: a x y (in order)
Merged in order -> a a x a b y = s3

Here is the core choice at each step. To match the next letter of s3, it must come from either s1 or s2.

next letter of s3

does it match next of s1

does it match next of s2

take from s1, advance i

take from s2, advance j

keep solving the rest

🐒 Approach 1: Try Both Sources (Brute Force)

The idea in one line: walk two pointers and try taking the next letter of s3 from s1 or from s2.

The idea:

  • Pointer i is the position in s1. Pointer j is the position in s2.
  • The position in s3 is always i + j, because together they have used i + j letters.

How it works:

  • Look at the next letter of s3.
  • If it equals the next letter of s1, try taking it from s1.
  • If it equals the next letter of s2, try taking it from s2.
  • If either path leads to a full match, the answer is true.

Why it is weak:

  • At each step you may branch into two paths.
  • With long strings the number of paths grows close to 2 to the power of the length.
  • Far too slow.

Here is the plain recursion code:

interleaving_string_recursion.py
def is_interleave(s1, s2, s3):
if len(s1) + len(s2) != len(s3):
return False
def dfs(i, j):
k = i + j
if k == len(s3):
return True
take_s1 = i < len(s1) and s1[i] == s3[k] and dfs(i + 1, j)
take_s2 = j < len(s2) and s2[j] == s3[k] and dfs(i, j + 1)
return take_s1 or take_s2
return dfs(0, 0)

🧠 Approach 2: Memoization (Better)

The idea in one line: cache the answer for each (i, j) pair so paths share work.

The idea:

  • Both i and j can be reached by many different paths.
  • But the answer for a given (i, j) pair never changes.

How it works:

  • Store the answer for each (i, j) pair the first time you compute it.
  • If you return to the same pair, read the saved answer.

Why it is fast:

  • There are only about (length of s1 + 1) * (length of s2 + 1) pairs.
  • The work is bounded by the size of that grid. This is memoization.

Here is the memoized recursion:

interleaving_string_memo.py
from functools import lru_cache
def is_interleave(s1, s2, s3):
if len(s1) + len(s2) != len(s3):
return False
@lru_cache(None)
def dfs(i, j):
k = i + j
if k == len(s3):
return True
return (
i < len(s1) and s1[i] == s3[k] and dfs(i + 1, j)
) or (
j < len(s2) and s2[j] == s3[k] and dfs(i, j + 1)
)
return dfs(0, 0)

πŸš€ Approach 3: 2D Boolean Table (Best)

The idea in one line: fill a grid where each cell is true if two prefixes interleave into a prefix of s3.

The idea:

  • Make a table dp where dp[i][j] is true if the first i of s1 and first j of s2 form the first i + j of s3.
  • The start dp[0][0] is true. Zero letters from each forms an empty string, matching the empty start of s3.

How it works:

  • To reach dp[i][j], the last letter of the s3 prefix came from one of two places.
  • It came from s1 if that letter equals the i-th letter of s1 and dp[i-1][j] was true.
  • It came from s2 if that letter equals the j-th letter of s2 and dp[i][j-1] was true.
  • If either works, dp[i][j] is true.

Why it is best:

  • Each cell is filled once, so the time is O(n times m).
  • No recursion. The answer is dp[len(s1)][len(s2)], whether all of s1 and all of s2 form all of s3.

Steps to Solve

  1. If len(s1) + len(s2) is not len(s3), return false.
  2. Make a dp table of size (len(s1)+1) by (len(s2)+1), all false.
  3. Set dp[0][0] = true.
  4. Fill the first row and column. They handle using letters from only one string.
  5. For each cell, check the match from s1 (dp[i-1][j]) and the match from s2 (dp[i][j-1]).
  6. Return the bottom-right cell dp[len(s1)][len(s2)].

This Python version builds a list of lists for the boolean grid.

interleaving_string.py
def is_interleave(s1, s2, s3):
n, m = len(s1), len(s2)
if n + m != len(s3): # lengths must add up
return False
dp = [[False] * (m + 1) for _ in range(n + 1)]
dp[0][0] = True # empty + empty = empty
for i in range(n + 1):
for j in range(m + 1):
if i == 0 and j == 0:
continue
from_s1 = False
from_s2 = False
if i > 0 and s1[i - 1] == s3[i + j - 1]:
from_s1 = dp[i - 1][j] # last letter came from s1
if j > 0 and s2[j - 1] == s3[i + j - 1]:
from_s2 = dp[i][j - 1] # last letter came from s2
dp[i][j] = from_s1 or from_s2
return dp[n][m]
print(is_interleave("aab", "axy", "aaxaby"))

The output of the above code will be:

True

Let us walk through the Python version line by line. The grid is the whole idea.

n, m = len(s1), len(s2)
if n + m != len(s3):
return False

We grab the lengths. If s1 and s2 together do not have the same number of letters as s3, no mix is possible. We stop right away.

dp = [[False] * (m + 1) for _ in range(n + 1)]
dp[0][0] = True

We make a grid with one extra row and one extra column. The extra slots stand for using zero letters. Here dp[i][j] means the first i letters of s1 and the first j letters of s2 form the first i + j letters of s3. We set dp[0][0] to True. Using nothing from each makes an empty string, which matches the empty start.

for i in range(n + 1):
for j in range(m + 1):
if i == 0 and j == 0:
continue

We visit every cell. We skip the start cell because we already set it.

if i > 0 and s1[i - 1] == s3[i + j - 1]:
from_s1 = dp[i - 1][j]

This asks: could the last matched letter have come from s1? It can only come from s1 if the i-th letter of s1 equals the current letter of s3. And the smaller problem dp[i-1][j] must already be true. Note s3[i + j - 1] is the current letter, because i + j letters are used so far.

if j > 0 and s2[j - 1] == s3[i + j - 1]:
from_s2 = dp[i][j - 1]

The mirror case. The last letter could have come from s2 if its letter matches and dp[i][j-1] was true.

dp[i][j] = from_s1 or from_s2

The cell is true if either source could supply the last letter. We only need one valid path.

return dp[n][m]

The bottom-right cell answers the full question. Can all of s1 and all of s2 interleave into all of s3?

Here is the filled grid for our example. A T means that cell is reachable. The bottom-right T is the answer.

dp[0][0]=T (empty)

row i uses letters of s1

col j uses letters of s2

dp[i][j] true if s1 letter matches and dp[i-1][j] true

dp[i][j] true if s2 letter matches and dp[i][j-1] true

dp[3][3]=T -> answer true

⏱️ Time and Space Complexity

Recursion can branch twice at each step, so it is O(2^(n+m)) time. Memoization and the 2D table both visit each (i, j) cell once. There are (n+1) * (m+1) cells. So both are O(n * m) time. The table uses O(n * m) space, but you can shrink it to one row if you want. Here n is the length of s1 and m is the length of s2.

Approach Time Complexity Space Complexity
Recursion (try both sources) O(2^(n+m)) O(n + m)
Memoization over (i, j) O(n * m) O(n * m)
2D boolean tabulation O(n * m) O(n * m)

Tip

A common trap is to match greedily from left to right. That fails when both strings start with the same letter. The grid avoids the trap because it keeps both choices alive at once. Always model interleaving as a 2D grid.

🧩 Key Takeaways

  • βœ… The position in s3 is always i + j, so you only track two pointers.
  • βœ… dp[i][j] is true if the first i of s1 and first j of s2 form the first i + j of s3.
  • βœ… A cell is true if the last letter could come from s1 or from s2.
  • βœ… A greedy left to right match fails. The grid keeps both choices open.
  • βœ… The answer sits in the bottom-right cell dp[len(s1)][len(s2)].

Check Your Knowledge

4 questions Show quiz Hide quiz

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does the Interleaving String problem check?

    Why: It asks if s3 can be formed by interleaving s1 and s2 while keeping the letter order inside each.

  2. 2

    In the dp grid, what does dp[i][j] mean?

    Why: dp[i][j] is true when the prefixes of length i and j interleave into the prefix of s3 of length i+j.

  3. 3

    Which letter of s3 do we compare against at cell (i, j)?

    Why: Together i + j letters are used, so the current letter being placed is s3[i + j - 1].

  4. 4

    Why does a greedy left to right match fail?

    Why: A greedy choice may pick the wrong source; the grid keeps both options alive so it never dead-ends.

πŸš€ What’s Next?