The problem
You get three strings s1, s2 and s3. Return True if s3 can be formed by interleaving s1 and s2: taking all characters of both, keeping the order of each string, but mixing them in any way.
Input: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
Output: TrueOne way: aa (from s1) + dbbc (s2) + bc (s1) + a (s2) + c (s1) gives aadbbcbcac.
How to think about it
Start with brute force. Walk through s3 and, at each character, try taking it from s1 or from s2 when both match. Every tie branches, so the worst case is O(2ᵐ⁺ⁿ).
Why greedy fails. When both strings offer the same next character, you don’t know which to take. Picking s1 first can dead-end later where picking s2 would have worked. That ambiguity is what DP resolves.
The key insight: the state is just how much of each string you’ve used. If you’ve used i characters of s1 and j of s2, you’ve built exactly s3[:i+j]. Let dp[i][j] mean “that is possible”. The last character s3[i+j-1] came either from s1[i-1] (so dp[i-1][j] must be true) or from s2[j-1] (so dp[i][j-1] must be true). So dp[i][j] = (dp[i-1][j] and s1[i-1] == s3[i+j-1]) or (dp[i][j-1] and s2[j-1] == s3[i+j-1]).
One row is enough. Each cell needs the cell above and the cell to the left, so a single array dp[j] works: before the update it holds “above”, and dp[j-1] is already “left”.
Which pattern is this? 2-D DP over two sequences, with a boolean cell. The tell is “can these two sequences combine into a third”, where ties make greedy unsafe.
Approach
- If
len(s1) + len(s2) != len(s3), returnFalse. - Fill row 0 (no characters of
s1):dp[0] = True, anddp[j]is true whiles2[:j]equalss3[:j]. - For each
ifrom 1: updatedp[0](onlys1used so far), then eachdp[j]with the recurrence. - Return
dp[-1].
Solution
def is_interleave(s1, s2, s3):
if len(s1) + len(s2) != len(s3): return False
dp = [False]*(len(s2)+1); dp[0] = True # dp[j] = can s1[:i], s2[:j] form s3[:i+j]
for j in range(1, len(s2)+1): dp[j] = dp[j-1] and s2[j-1] == s3[j-1] # row 0: only s2
for i in range(1, len(s1)+1):
dp[0] = dp[0] and s1[i-1] == s3[i-1] # column 0: only s1
for j in range(1, len(s2)+1):
dp[j] = (dp[j] and s1[i-1] == s3[i+j-1]) or (dp[j-1] and s2[j-1] == s3[i+j-1]) # from above (s1) or left (s2)
return dp[-1]
# is_interleave("aabcc", "dbbca", "aadbbcbcac") -> TrueDry run
For the example, the dp row after each i (1 = True, 0 = False; columns j = 0..5):
i (chars of s1 used) |
dp |
|---|---|
| 0 | [1, 0, 0, 0, 0, 0] |
| 1 | [1, 0, 0, 0, 0, 0] |
| 2 | [1, 1, 1, 1, 1, 0] |
| 3 | [0, 1, 1, 0, 1, 0] |
| 4 | [0, 0, 1, 1, 1, 1] |
| 5 | [0, 0, 0, 1, 0, 1] |
The last cell of the last row is 1, so the answer is True. With s3 = "aadbbbaccc" it would be False.
Complexity
- Time: O(m · n). One update per pair
(i, j). - Space: O(n). One row of length
len(s2) + 1.
Edge cases and pitfalls
- Length mismatch returns
Falseimmediately; skipping this check can index past the end ofs3. - All empty (
"", "", "") returnsTrue. - Greedy “take from s1 when it matches” fails on inputs with shared characters, which is exactly the hard case.
- The
s3index isi + j - 1, notiorj; off-by-one here is the usual bug.
Related problems
- Longest Common Subsequence (LeetCode 1143): the same prefix-pair table, with numbers instead of booleans.
- Edit Distance (LeetCode 72): another two-string DP where each cell looks up, left and diagonally.
- Unique Paths (LeetCode 62): the same “above or left” shape; here a path through the grid is a way to interleave.
- 1.Unique Paths (LeetCode 62)
- 2.Unique Paths II (LeetCode 63)
- 3.Minimum Path Sum (LeetCode 64)
- 4.Longest Common Subsequence (LeetCode 1143)
- 5.Edit Distance (LeetCode 72)
- 6.Maximal Square (LeetCode 221)
- 7.Longest Palindromic Substring (LeetCode 5)
- 8.Longest Palindromic Subsequence (LeetCode 516)
- 9.Interleaving String (LeetCode 97)
- 10.Distinct Subsequences (LeetCode 115)
- 11.Dungeon Game (LeetCode 174)
- 12.Regular Expression Matching (LeetCode 10)