Distinct Subsequences
Difficulty: Hard
You're given two strings, s and t. Count how many distinct ways you can pick out a subsequence of s (deleting some characters, keeping the rest in order) so that it spells out t exactly.
Two ways are different if they delete different characters from s, even if the leftover letters look the same.
Examples
Input: s = "rabbbit", t = "rabbit"
Output: 3
There are 3 ways to delete two of the three b's from s to spell out rabbit.
Input: s = "babgbag", t = "bag"
Output: 5
Constraints
1 <= s.length, t.length <= 1000
Both strings contain only English letters
Approach
Build a table where cell (i, j) means: "using only the first i characters of s, how many distinct ways are there to spell out the first j characters of t?"
The i-th character of s can always be skipped (deleted), which alone contributes as many ways as (i-1, j) already found. On top of that, if the i-th character of s happens to equal the j-th character of t, it can also be used to match that character, contributing however many ways (i-1, j-1) found. Add both possibilities together — they're two genuinely different sets of deletions, so they don't overlap.
The base case: matching an empty t (column 0) is always possible in exactly one way for any prefix of s — delete everything.
Solutions
Brute Force — Recursion
Walk s and t from the front together. At each step, always try skipping the current character of s; additionally, if it matches the current character of t, try consuming both. Sum the ways found down each branch.
function numDistinct(s, t) {
function solve(i, j) {
if (j === t.length) return 1;
if (i === s.length) return 0;
let ways = solve(i + 1, j); // skip s[i]
if (s[i] === t[j]) {
ways += solve(i + 1, j + 1); // use s[i] to match t[j]
}
return ways;
}
return solve(0, 0);
}Time: O(2^m), where m is the length of s · Space: O(m) — recursion depth
Optimal — Bottom-Up 2D Table
Build a table over (prefix length of s, prefix length of t), seeding column 0 with 1 (an empty t is always matched exactly one way), then filling every other cell as 'skip' plus, when characters match, 'use.'
function numDistinct(s, t) {
const m = s.length;
const n = t.length;
const table = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
for (let i = 0; i <= m; i++) {
table[i][0] = 1; // empty t is always formed exactly one way: delete everything
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
table[i][j] = table[i - 1][j]; // skip s[i - 1]
if (s[i - 1] === t[j - 1]) {
table[i][j] += table[i - 1][j - 1]; // also use s[i - 1] to match t[j - 1]
}
}
}
return table[m][n];
}Time: O(m * n) · Space: O(m * n)