Interleaving String
Table of Contents + β
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
s1or withins2. - The length of
s3must equal the length ofs1plus the length ofs2, or the answer isfalseright away. - Return
trueorfalse.
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 = s3Here is the core choice at each step. To match the next letter of s3, it must come from either s1 or s2.
π’ 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
iis the position ins1. Pointerjis the position ins2. - The position in
s3is alwaysi + j, because together they have usedi + jletters.
How it works:
- Look at the next letter of
s3. - If it equals the next letter of
s1, try taking it froms1. - If it equals the next letter of
s2, try taking it froms2. - 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
2to the power of the length. - Far too slow.
Here is the plain recursion code:
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
iandjcan 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:
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
dpwheredp[i][j]istrueif the firstiofs1and firstjofs2form the firsti + jofs3. - The start
dp[0][0]istrue. Zero letters from each forms an empty string, matching the empty start ofs3.
How it works:
- To reach
dp[i][j], the last letter of thes3prefix came from one of two places. - It came from
s1if that letter equals thei-th letter ofs1anddp[i-1][j]was true. - It came from
s2if that letter equals thej-th letter ofs2anddp[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 ofs1and all ofs2form all ofs3.
Steps to Solve
- If
len(s1) + len(s2)is notlen(s3), returnfalse. - Make a
dptable of size(len(s1)+1)by(len(s2)+1), allfalse. - Set
dp[0][0] = true. - Fill the first row and column. They handle using letters from only one string.
- For each cell, check the match from
s1(dp[i-1][j]) and the match froms2(dp[i][j-1]). - Return the bottom-right cell
dp[len(s1)][len(s2)].
This Python version builds a list of lists for the boolean grid.
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:
TrueLet 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 FalseWe 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] = TrueWe 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: continueWe 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_s2The 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.
β±οΈ 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
s3is alwaysi + j, so you only track two pointers. - β
dp[i][j]is true if the firstiofs1and firstjofs2form the firsti + jofs3. - β
A cell is true if the last letter could come from
s1or froms2. - β 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
Test what you learned. Pick an answer for each question, then click Check.
- 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
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
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
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.