String DP: The Art of Transforming Words 🪄
The Magic Spell Book Analogy
Imagine you have a magic spell book. Each spell can transform one word into another. But magic costs energy! The best wizards find spells that use the least energy to make transformations.
String DP is exactly like this. We figure out the smartest way to:
- Change one word into another (Edit Distance)
- Break words into pieces (Word Break)
- Match patterns with wildcards (Wildcard Matching)
- Handle complex patterns (Regular Expression Matching)
What is DP on Strings?
Dynamic Programming on Strings means solving problems by:
- Looking at small pieces of strings first
- Building up answers to bigger pieces
- Using a table to remember what we already solved
The Simple Idea
Think of two words as train tracks running side by side.
Word 1: C - A - T
Word 2: C - U - T
We compare them character by character. Each comparison gives us a small answer. We combine small answers into the big answer!
1. Edit Distance (Levenshtein Distance)
The Story
You’re a text message fixer. Your friend types “KITTEN” but meant “SITTING”. How many fixes do you need?
A fix can be:
- Insert a letter
- Delete a letter
- Replace a letter
Visual Journey
graph TD A["KITTEN"] --> B["SITTEN<br/>Replace K→S"] B --> C["SITTIN<br/>Replace E→I"] C --> D["SITTING<br/>Insert G"]
Answer: 3 fixes!
How It Works
We build a table. Each cell answers: “How many edits to change first X letters into first Y letters?”
"" S I T T I N G
"" 0 1 2 3 4 5 6 7
K 1 1 2 3 4 5 6 7
I 2 2 1 2 3 4 5 6
T 3 3 2 1 2 3 4 5
T 4 4 3 2 1 2 3 4
E 5 5 4 3 2 2 3 4
N 6 6 5 4 3 3 2 3
Bottom-right = 3 ✨
The Magic Formula
For each cell, we pick the smallest of:
if s1[i] == s2[j]:
dp[i][j] = dp[i-1][j-1] # Same letter, no cost!
else:
dp[i][j] = 1 + min(
dp[i-1][j], # Delete
dp[i][j-1], # Insert
dp[i-1][j-1] # Replace
)
Real Example
Change “CAT” to “CUT”:
| “” | C | U | T | |
|---|---|---|---|---|
| “” | 0 | 1 | 2 | 3 |
| C | 1 | 0 | 1 | 2 |
| A | 2 | 1 | 1 | 2 |
| T | 3 | 2 | 2 | 1 |
Answer: 1 (just replace A with U)
2. Word Break
The Story
Imagine you receive a message with no spaces:
"ilikecoding"
Can you break it into real words from a dictionary?
Dictionary: [“i”, “like”, “coding”, “code”, “ice”, “cream”]
Answer: “i” + “like” + “coding” = ✅ Yes!
The Challenge
"catsanddog" with [“cat”, “cats”, “and”, “sand”, “dog”]
Multiple ways exist:
- “cats” + “and” + “dog”
- “cat” + “sand” + “dog”
How It Works
We check every position: “Can I reach here using dictionary words?”
graph TD A["Position 0<br/>Start ✅"] --> B["Position 1<br/>&#39;i&#39; found ✅"] B --> C["Position 5<br/>&#39;like&#39; found ✅"] C --> D["Position 11<br/>&#39;coding&#39; found ✅"]
The Simple Code Idea
def wordBreak(s, wordDict):
n = len(s)
dp = [False] * (n + 1)
dp[0] = True # Empty string = valid!
for i in range(1, n + 1):
for word in wordDict:
wlen = len(word)
if i >= wlen:
# Check if word fits here
if dp[i - wlen]:
if s[i - wlen:i] == word:
dp[i] = True
break
return dp[n]
Walk Through “leetcode”
Dictionary: [“leet”, “code”]
| Position | Substring Check | dp value |
|---|---|---|
| 0 | (start) | True |
| 1-3 | No match | False |
| 4 | “leet” ✅ | True |
| 5-7 | No match | False |
| 8 | “code” ✅ | True |
Answer: True! 🎉
3. Wildcard Matching
The Story
You’re playing a guessing game. You have pattern cards:
?= matches any single letter*= matches anything (even nothing!)
Pattern: "c*t"
Matches: “cat”, “cut”, “cart”, “ct”, “coconut”
The Tricky Part
* is greedy! It can match zero letters or many letters.
Pattern: "a*b"
- “ab” ✅ (* matches nothing)
- “aXb” ✅ (* matches “X”)
- “aXXXb” ✅ (* matches “XXX”)
- “abc” ❌ (doesn’t end with b)
Visual Example
graph TD A["Pattern: a*b<br/>Text: aXYb"] --> B["&#39;a&#39; matches &#39;a&#39; ✅"] B --> C["&#39;*&#39; matches &#39;X&#39;"] C --> D["&#39;*&#39; matches &#39;Y&#39;"] D --> E["&#39;b&#39; matches &#39;b&#39; ✅"]
The DP Table Approach
Build a table where dp[i][j] = “Does pattern[0:i] match text[0:j]?”
def isMatch(s, p):
m, n = len(s), len(p)
dp = [[False] * (n + 1)
for _ in range(m + 1)]
dp[0][0] = True
# Handle leading stars
for j in range(1, n + 1):
if p[j-1] == '*':
dp[0][j] = dp[0][j-1]
for i in range(1, m + 1):
for j in range(1, n + 1):
if p[j-1] == '*':
# Star matches empty OR
# one more character
dp[i][j] = (dp[i][j-1] or
dp[i-1][j])
elif p[j-1] == '?' or \
p[j-1] == s[i-1]:
dp[i][j] = dp[i-1][j-1]
return dp[m][n]
Quick Example
Pattern: "?a*"
Text: "bat"
| “” | ? | a | * | |
|---|---|---|---|---|
| “” | T | F | F | F |
| b | F | T | F | F |
| a | F | F | T | T |
| t | F | F | F | T |
Answer: True! ✨
4. Regular Expression Matching
The Story
This is Wildcard Matching’s older sibling. More powerful, more complex!
Special characters:
.= matches any single character (like?)*= the letter before it can appear 0 or more times
The Key Difference
In regex, * doesn’t stand alone. It modifies the previous character!
| Pattern | Meaning |
|---|---|
a* |
zero or more 'a’s |
.* |
zero or more of anything |
ab*c |
‘a’, then any number of 'b’s, then ‘c’ |
Examples
| Pattern | Text | Match? |
|---|---|---|
a* |
“” | ✅ (zero a’s) |
a* |
“aaa” | ✅ (three a’s) |
a.b |
“acb” | ✅ |
a.*b |
“aXYZb” | ✅ |
c*a*b |
“aab” | ✅ (zero c’s, two a’s) |
Visual Flow
graph TD A["Pattern: a*b<br/>Text: aaab"] --> B["&#39;a*&#39; matches &#39;aaa&#39;"] B --> C["&#39;b&#39; matches &#39;b&#39; ✅"]
The DP Solution
def isMatch(s, p):
m, n = len(s), len(p)
dp = [[False] * (n + 1)
for _ in range(m + 1)]
dp[0][0] = True
# Handle patterns like a*, a*b*, etc.
for j in range(2, n + 1):
if p[j-1] == '*':
dp[0][j] = dp[0][j-2]
for i in range(1, m + 1):
for j in range(1, n + 1):
if p[j-1] == '*':
# Option 1: ignore x* entirely
dp[i][j] = dp[i][j-2]
# Option 2: use x* if matches
if p[j-2] == '.' or \
p[j-2] == s[i-1]:
dp[i][j] = (dp[i][j] or
dp[i-1][j])
elif p[j-1] == '.' or \
p[j-1] == s[i-1]:
dp[i][j] = dp[i-1][j-1]
return dp[m][n]
Step Through “aa” with “a*”
| “” | a | * | |
|---|---|---|---|
| “” | T | F | T |
| a | F | T | T |
| a | F | F | T |
Why is dp[2][2] = True?
a* means “zero or more a’s”. Two a’s? Perfect match! ✅
Comparing the Four Patterns
| Problem | Goal | Special Chars |
|---|---|---|
| Edit Distance | Min changes | None |
| Word Break | Can split? | None |
| Wildcard | Pattern match | ? * |
| Regex | Pattern match | . x* |
The Universal DP Recipe
All four problems follow the same recipe:
- Create a table (usually 2D)
- Define base cases (empty strings)
- Fill cell by cell (small to big)
- Return the answer (usually bottom-right)
Memory Trick
Think of it as filling a spreadsheet:
- Rows = characters from string 1
- Columns = characters from string 2 (or pattern)
- Each cell = answer for that prefix combination
Practice Problems to Try
- Edit Distance: “horse” → “ros” (Answer: 3)
- Word Break: “applepenapple” with [“apple”, “pen”]
- Wildcard: Pattern “a*c?e” on “abcde”
- Regex: Pattern “.*” matches everything!
You’ve Got This! 🚀
String DP might seem scary at first. But now you know:
- It’s just comparing characters step by step
- We use tables to avoid repeating work
- Each problem has its own special rules
Start with Edit Distance. It’s the friendliest. Once that clicks, the others will follow!
Remember: Every expert was once a beginner. Keep practicing! 💪
