Dynamic Programming on Strings Pattern
The Dynamic Programming on Strings pattern compares two sequences (or a string against itself) by filling a 2D table dp[i][j] indexed by prefix lengths. Each cell extends a smaller subproblem by one character on either side, replacing exponential character-by-character branching with O(m * n) time and O(m * n) space.
By Alex Chen, Founder, InterviewChamp.AI · Last verified
Complexity
- Time
- O(m * n)
- Space
- O(m * n)
When to use Dynamic Programming on Strings
- The problem compares two strings and asks for an edit distance, longest common substructure, or a count of matching pairs.
- The answer involves a single string but you need to consider every substring or every (i, j) prefix pair (palindromic subsequence, palindrome partitioning).
- Characters can be matched, inserted, deleted, or substituted, and each operation has a fixed cost or contribution.
- Brute-force recursion on (i, j) overlaps because the same prefix pair recurs through different match/skip orderings.
- The strings are short to medium (up to ~1000 characters) so an m * n table fits in memory.
Template
function stringDP(s1, s2) {
const m = s1.length;
const n = s2.length;
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (s1[i - 1] === s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}Example LeetCode problems
| # | Problem | Difficulty | Frequency |
|---|---|---|---|
| 1143 | Longest Common Subsequence | medium | foundational |
| 72 | Edit Distance | medium | company favorite |
| 10 | Regular Expression Matching | hard | classic |
| 44 | Wildcard Matching | hard | frequently asked |
| 516 | Longest Palindromic Subsequence | medium | frequently asked |
| 115 | Distinct Subsequences | hard | less common |
| 97 | Interleaving String | medium | company favorite |
| 583 | Delete Operation for Two Strings | medium | foundational |
Common mistakes
- Indexing dp[i][j] by string indices instead of by prefix lengths, then forgetting to add the +1 offset that makes the empty-prefix base case fit cleanly.
- Off-by-one when reading s1[i - 1] vs s1[i] inside the loop body, producing answers shifted by one character.
- On Edit Distance, missing one of the three operations (insert, delete, substitute) in the recurrence, which gives a value larger than the true minimum.
- On Regular Expression Matching, treating the '*' as 'one or more' instead of 'zero or more of the preceding element' and mishandling the zero-occurrence case.
- Allocating an O(m * n) table when only the previous row is needed, blowing memory on long inputs.
Frequently asked questions
What is the difference between Longest Common Subsequence and Longest Common Substring?
Subsequence allows non-contiguous matches as long as relative order is preserved; substring requires contiguous matches. The recurrences differ: subsequence carries the diagonal value forward when characters don't match, substring resets to zero on a mismatch and tracks a running max.
How do I reconstruct the actual edit script for Edit Distance?
After filling the dp table, walk back from dp[m][n] to dp[0][0]. At each cell, check which neighbor produced the current value — the diagonal cell means match or substitute, the cell above means delete, the cell to the left means insert. Record each operation as you walk.
When can I drop the 2D table down to O(n) space?
Whenever each cell only depends on the previous row (dp[i-1][...]) and the cell immediately to the left (dp[i][j-1]). Save a single scalar for the diagonal value before overwriting it, then keep a rolling row of length n + 1. Edit Distance, LCS, and Distinct Subsequences all support this optimization.
How does Longest Palindromic Subsequence differ from a standard two-string DP?
It compares the string against its reverse, so the recurrence is the same as LCS — but on s and s.reversed(). An alternative formulation indexes dp[i][j] by the substring s[i..j], filling shorter substrings first. Both are O(n^2) and produce the same answer.
Solve it now
Free. No sign-up. Python and JavaScript run instantly in your browser.
Practice these live with InterviewChamp.AI
Drill the Dynamic Programming on Strings pattern under real interview conditions with instant feedback on your reasoning, complexity claims, and code.
Practice these live with InterviewChamp.AI →