AlgoMaster Logo

Interleaving String

Last Updated: March 28, 2026

medium
5 min read

Understanding the Problem

At first glance, this looks like a simple string matching problem: just check if we can pick characters from s1 and s2 in order to form s3. But the tricky part is the branching. When the next character in s3 matches both the next available character in s1 and s2, we have a choice. Picking the wrong one can lead us down a dead end even though a valid interleaving exists.

For example, with s1 = "aa", s2 = "ab", and s3 = "aaba", a greedy approach that always picks from s1 first would consume both 'a's from s1, then need 'b' from s2, but s3[2] is 'b' and s3[3] is 'a'. That works! But change s3 to "abaa" and the greedy approach breaks. The choices interact in complex ways because future characters constrain which path is valid.

The key observation: the state of our interleaving process is completely determined by how many characters we have used from s1 and s2. If we have used i characters from s1 and j characters from s2, then we must be at position i + j in s3. So the problem reduces to finding a path through a 2D grid from (0, 0) to (len(s1), len(s2)).

Key Constraints:

  • 0 <= s1.length, s2.length <= 100 → With m and n both up to 100, O(m * n) is 10,000 operations, which is extremely fast. A 2D DP approach is the natural fit.
  • 0 <= s3.length <= 200 → Since s3's length must equal s1.length + s2.length for interleaving to be possible, this gives us a quick early return check.
  • Lowercase English letters only → No special character handling needed. But repeating characters create the ambiguity that makes this problem interesting.

Approach 1: Recursion with Memoization (Top-Down DP)

Intuition

The most natural way to think about this problem: at each step, we are at some position in s1 (index i), some position in s2 (index j), and the corresponding position in s3 is i + j. We need to decide whether to take the next character from s1 or s2.

If s1[i] matches s3[i + j], we can try consuming that character from s1 and recursing with (i + 1, j). Similarly, if s2[j] matches s3[i + j], we try (i, j + 1). If either path succeeds, the answer is true.

The "aha moment" here is that the state (i, j) completely determines where we are in the problem. We do not need to track our position in s3 separately because it is always i + j. And since many different orderings of choices can lead to the same (i, j) state, memoization eliminates redundant work.

Algorithm

  1. If the lengths of s1 and s2 do not sum to the length of s3, return false immediately.
  2. Define a recursive function dfs(i, j) that returns whether s3[i+j...] can be formed by interleaving s1[i...] and s2[j...].
  3. Base case: if i == len(s1) and j == len(s2), we have matched all of s3, so return true.
  4. If s1[i] == s3[i + j], recursively check dfs(i + 1, j).
  5. If s2[j] == s3[i + j], recursively check dfs(i, j + 1).
  6. Memoize results for each (i, j) pair to avoid recomputation.

Example Walkthrough

1dfs(0,0): s3[0]='a', s1[0]='a' match, s2[0]='d' no match. Try s1.
0
a
i+j=0
1
a
2
d
3
b
4
b
5
c
6
b
7
c
8
a
9
c
1/6

Code

The recursion has function call overhead and stack space. What if we filled the DP table iteratively instead, eliminating the recursion entirely?

Approach 2: Bottom-Up DP (2D Table)

Intuition

The top-down approach reveals the structure: we have a 2D state space where dp[i][j] means "can the first i characters of s1 and the first j characters of s2 interleave to form the first i + j characters of s3?"

Instead of starting from (0, 0) and recursing forward, we fill the table from the base case outward. The cell dp[i][j] is true if either:

  • dp[i - 1][j] is true AND s1[i - 1] == s3[i + j - 1] (we extend by taking one more character from s1), or
  • dp[i][j - 1] is true AND s2[j - 1] == s3[i + j - 1] (we extend by taking one more character from s2).

Algorithm

  1. If len(s1) + len(s2) != len(s3), return false.
  2. Create a boolean table dp of size (m + 1) x (n + 1).
  3. Set dp[0][0] = true (empty strings interleave to form empty string).
  4. Fill the first row: dp[0][j] is true if all characters of s2[0..j-1] match s3[0..j-1].
  5. Fill the first column: dp[i][0] is true if all characters of s1[0..i-1] match s3[0..i-1].
  6. For each cell dp[i][j], check if we can arrive from the top (using s1) or from the left (using s2).
  7. Return dp[m][n].

Example Walkthrough

1Initialize dp[0][0]=true (empty strings interleave to form empty)
0
1
2
3
4
5
0
true
false
false
false
false
false
1
false
false
false
false
false
false
2
false
false
false
false
false
false
3
false
false
false
false
false
false
4
false
false
false
false
false
false
5
false
false
false
false
false
false
1/7

Code

The 2D table uses O(m * n) space, but when computing row i, we only look at row i - 1 and the current row. Can we collapse the table into a single 1D array?

Approach 3: Space-Optimized DP (1D Array)

Intuition

Looking at the recurrence more carefully, dp[i][j] depends on only two cells: dp[i-1][j] (directly above) and dp[i][j-1] (directly to the left). This is the classic pattern that allows us to reduce a 2D DP table to a 1D array.

We maintain a single array dp of size n + 1, where dp[j] represents the current row's value. Before we update dp[j] for the current row, it still holds the value from the previous row, which is exactly dp[i-1][j]. And dp[j-1] has already been updated for the current row (since we process left to right), so it represents dp[i][j-1].

Algorithm

  1. If len(s1) + len(s2) != len(s3), return false.
  2. Create a 1D boolean array dp of size n + 1.
  3. Set dp[0] = true.
  4. Fill the first row: for each j, dp[j] = dp[j-1] && s2[j-1] == s3[j-1].
  5. For each row i from 1 to m:
    • Update dp[0] for this row: dp[0] = dp[0] && s1[i-1] == s3[i-1].
    • For each column j from 1 to n: dp[j] = (dp[j] && s1[i-1] == s3[i+j-1]) || (dp[j-1] && s2[j-1] == s3[i+j-1]).
  6. Return dp[n].

Example Walkthrough

1Initialize first row: dp[0]=T, dp[1..5]=F (s2[0]='d'!=s3[0]='a')
0
true
1
false
2
false
3
false
4
false
5
false
1/6

Code