String DP - Intuition Guide¶
The Mental Model: Filling a Grid¶
Imagine you have two strings written along the edges of a grid: - First string s along the rows (top to bottom) - Second string t along the columns (left to right)
Each cell (i, j) represents the answer for comparing s[0:i] with t[0:j].
"" a b c
βββββ¬ββββ¬ββββ¬ββββ
"" β 0 β 0 β 0 β 0 β
βββββΌββββΌββββΌββββ€
a β 0 β 1 β 1 β 1 β
βββββΌββββΌββββΌββββ€
c β 0 β 1 β 1 β 2 β
βββββ΄ββββ΄ββββ΄ββββ
You fill this grid row by row, and each cell only needs its neighbors: - Diagonal (top-left): both strings shrink - Up: first string shrinks - Left: second string shrinks
Why String DP?¶
String DP works because string comparison has optimal substructure: - The best answer for long strings depends only on answers for shorter strings - We can systematically build from empty strings to full strings
Core Insight¶
The transition depends on whether current characters match:
Match: s[i-1] == t[j-1]
β Look diagonal (both consumed)
Mismatch: s[i-1] != t[j-1]
β Look up, left, or diagonal (try all options)
Pattern 1: LCS (Longest Common Subsequence)¶
The insight: If characters match, include them. If not, try skipping either character.
s = "ace", t = "abcde"
"" a b c d e
βββββ¬ββββ¬ββββ¬ββββ¬ββββ¬ββββ
"" β 0 β 0 β 0 β 0 β 0 β 0 β
βββββΌββββΌββββΌββββΌββββΌββββ€
a β 0 β 1 β 1 β 1 β 1 β 1 β a=a: +1
βββββΌββββΌββββΌββββΌββββΌββββ€
c β 0 β 1 β 1 β 2 β 2 β 2 β c=c: +1
βββββΌββββΌββββΌββββΌββββΌββββ€
e β 0 β 1 β 1 β 2 β 2 β 3 β e=e: +1
βββββ΄ββββ΄ββββ΄ββββ΄ββββ΄ββββ
βββ Answer: 3
The code:
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1] + 1 # Include this character
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) # Skip one char
Pattern 2: Edit Distance¶
The insight: Each cell is the minimum edits to transform. Three operations mean three choices.
s = "horse", t = "ros"
"" r o s
βββββ¬ββββ¬ββββ¬ββββ
"" β 0 β 1 β 2 β 3 β Insert all
βββββΌββββΌββββΌββββ€
h β 1 β 1 β 2 β 3 β hβ r: min(replace, delete, insert)
βββββΌββββΌββββΌββββ€
o β 2 β 2 β 1 β 2 β o=o: diagonal (no cost)
βββββΌββββΌββββΌββββ€
r β 3 β 2 β 2 β 2 β rβ s: +1
βββββΌββββΌββββΌββββ€
s β 4 β 3 β 3 β 2 β s=s: diagonal
βββββΌββββΌββββΌββββ€
e β 5 β 4 β 4 β 3 β Answer: 3
βββββ΄ββββ΄ββββ΄ββββ
The code:
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1] # No operation needed
else:
dp[i][j] = 1 + min(
dp[i-1][j-1], # Replace s[i-1] with t[j-1]
dp[i-1][j], # Delete s[i-1]
dp[i][j-1] # Insert t[j-1]
)
Pattern 3: Palindrome Subsequence¶
The insight: A palindrome reads the same forwards and backwards.
The LPS of string s = LCS of s and reverse(s).
Why does this work? Any common subsequence between s and its reverse must appear in both directions, making it a palindrome.
Pattern 4: Regex Matching¶
The insight: Handle * by considering "use it" vs "skip it".
s = "aab", p = "c*a*b"
c* β 0 or more 'c' (we use 0)
a* β 0 or more 'a' (we use 2)
b β exactly 'b' (we use 1)
Result: MATCH
The tricky part is *:
if p[j-1] == '*':
# Option 1: Use zero of p[j-2]
dp[i][j] = dp[i][j-2]
# Option 2: Use one or more of p[j-2]
if p[j-2] == '.' or p[j-2] == s[i-1]:
dp[i][j] = dp[i][j] or dp[i-1][j]
Common Mistakes¶
Mistake 1: Off-by-one indexing¶
# β Wrong: dp indices and string indices are off by 1
if s[i] == t[j]:
# β
Right: dp[i][j] corresponds to s[0:i], so character is s[i-1]
if s[i-1] == t[j-1]:
Mistake 2: Wrong base cases for Edit Distance¶
# β Wrong: Forgetting base cases
dp = [[0] * (n+1) for _ in range(m+1)]
# β
Right: Empty string requires i deletions or j insertions
for i in range(m+1):
dp[i][0] = i
for j in range(n+1):
dp[0][j] = j
Mistake 3: Regex * looks at wrong index¶
# β Wrong: Looking at j-1 for the character before *
if p[j-1] == s[i-1]:
# β
Right: * is at j-1, so the character is at j-2
if p[j-2] == s[i-1]:
Quick Pattern Recognition¶
| Clue | Pattern |
|---|---|
| "longest common subsequence" | LCS |
| "minimum operations to convert" | Edit Distance |
| "palindrome subsequence" | LCS with reverse |
| "match pattern with . or *" | Regex DP |
| "delete operation for two strings" | LCS-based |
Visual Summary¶
String DP: Compare s[0:i] with t[0:j]
βββββββββββββββ
β dp[i-1][j-1]βββββΊ Match: usually this
ββββββββ¬βββββββ
β
βββββββββββββββΌββββββββββββββ
β β β
βΌ βΌ βΌ
ββββββββββ βββββββββββββββ ββββββββββ
βdp[i-1] β β dp[i][j] β βdp[i] β
β [j] β β (current) β β [j-1] β
ββββββββββ βββββββββββββββ ββββββββββ
Delete s[i] Insert t[j]
β β
ββββββββββββ¬βββββββββββββββββββ
β
βΌ
Mismatch: combine
these options