Distinct Subsequences is a classic grid dynamic-programming problem. It teaches you to count possibilities — not just find one answer — by filling a 2D table where each cell builds on its neighbors.
Problem. Given two strings s and t, return the number of distinct subsequences of s that
equal t. A subsequence keeps characters in order but may skip some. The count can be large.
Example: s = "rabbbit", t = "rabbit" → 3. The three b characters in s give three ways to pick the
single b that t needs, and everything else lines up uniquely.
The slow way first
The brute-force idea is to generate every subsequence of s and count how many equal t. A string of length n has 2ⁿ subsequences, so this is exponential — hopeless for anything but tiny inputs.
The question to ask: as I scan s and t together, what smaller answer can I reuse? If I already know how many ways a shorter prefix of t fits inside a shorter prefix of s, I can extend that to the full strings one character at a time. That is exactly what a DP grid stores.
The idea: a counting grid
Build a table dp where dp[i][j] is the number of ways the first j characters of t appear as a subsequence inside the first i characters of s. The answer is the bottom-right cell.
For each cell we always have the option to skip s[i-1], which gives us dp[i-1][j]. If the current characters match (s[i-1] == t[j-1]), we additionally get the option to use s[i-1] to satisfy t[j-1], which adds the diagonal dp[i-1][j-1].
The base cases anchor everything: an empty t matches any prefix of s in exactly one way (delete everything), so the first column is all 1. An empty s cannot contain any non-empty t, so the rest of the top row is 0.
Walk through it
Step through the animation. The grid fills top to bottom, left to right. Watch the column for t's b: as s gains its first, second, and third b, that count climbs 1 → 2 → 3, because each extra b is another independent choice. The unique i and t at the end just carry that count straight down to the final cell, 3.
Pseudocode
let dp be an (m+1) x (n+1) grid of zeros # m = len(s), n = len(t)
for every row i:
dp[i][0] = 1 # empty t matches one way
for i from 1 to m:
for j from 1 to n:
dp[i][j] = dp[i-1][j] # always: skip s[i-1]
if s[i-1] == t[j-1]:
dp[i][j] += dp[i-1][j-1] # also: use s[i-1] for t[j-1]
return dp[m][n]The Python solution
def num_distinct(s, t):
m, n = len(s), len(t)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
dp[i][0] = 1
for i in range(1, m + 1):
for j in range(1, n + 1):
if s[i - 1] == t[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
else:
dp[i][j] = dp[i - 1][j]
return dp[m][n]dpis an(m+1) × (n+1)grid; the extra row and column hold the empty-string base cases.- The loop
dp[i][0] = 1fills the first column: an emptytis matched exactly one way for every prefix ofs. - When
s[i-1] == t[j-1], we sum two choices:dp[i-1][j-1]uses this character ofs, anddp[i-1][j]skips it. - When the characters differ, the only option is to skip
s[i-1], so we copydp[i-1][j]. - The answer is
dp[m][n], the bottom-right cell, after the whole grid is filled.
Complexity
| Case | Time | Notes |
|---|---|---|
| Brute force (all subsequences) | O(2^n) (slow) | exponential, enumerates every subsequence |
| Grid DP (this solution) | O(m * n) (moderate) | fill each cell once in O(1) |
O(m * n) (moderate)We trade an O(m × n) table for an enormous speedup. The space can be reduced to O(n) by keeping only the previous row, since each cell looks back only one row.
When this pattern shows up
When a problem asks you to count the number of ways (not just yes/no or a single best value) over two sequences, reach for a 2D DP grid. Edit Distance, Longest Common Subsequence, and Distinct Subsequences are all the same shape: a cell defined by its top, left, and diagonal neighbors.
Mind the index offset. dp[i][j] talks about the first i characters of s and first j of t, so the
actual characters being compared are s[i-1] and t[j-1]. Off-by-one mistakes here are the most common bug.
Practice
For s = 'rabbbit', t = 'rabbit', why is the answer 3 rather than 1?
1. What does dp[i][j] represent?
2. Why is the first column (empty t) all 1s?
3. When s[i-1] == t[j-1], what is dp[i][j]?
4. What is the time complexity of the grid solution?