Browse curriculum

Distinct Subsequences

Count the ways t appears as a subsequence of s with a 2D DP table: every letter of s is skipped, or used when it matches the next letter of t.

Problem Understanding

Distinct Subsequences: given strings s and t, return how many distinct subsequences of s equal t. A subsequence keeps letters in order but may drop any of them; two subsequences are different if they use different positions of s.

Example: s = "rabbbit", t = "rabbit" returns 3: drop any one of the three b's. s = "babgbag", t = "bag" returns 5.

The Intuition: Skip It or Use It

Walk through s one letter at a time. Each letter makes a choice:

  1. Skip it: the ways to spell t[..j] are the ways you already had without it, dp[i - 1][j].
  2. Use it, only if it equals t[j - 1]: it finishes t[..j], so add every way of spelling t[..j - 1] before it, dp[i - 1][j - 1].

The two choices never overlap (one uses position i - 1, the other does not), so their counts simply add. It is the same two-string table as Longest Common Subsequence and Interleaving String, with counts that add instead of a max or a true/false. Regular Expression Matching runs the same grid with pattern rules. The skip-or-take choice is the core of dynamic programming on sequences.

Interactive Walkthrough

t runs across the top and s down the left. The live cell is solid and the cells it adds are tinted: above for skip, diagonal for use. Try Choose 3 of 8 to see the table build binomial coefficients.

Dry Run Table

Input: s = "babgbag" (down), t = "bag" (across)

∅ b a g
∅ 1 0 0 0
b 1 1 0 0
a 1 1 1 0
b 1 2 1 0
g 1 2 1 1
b 1 3 1 1
a 1 3 4 1
g 1 3 4 5
  • Row 6 (a), column a: skip gives 1, use gives the 3 ways to spell "b" before it: 4.
  • Row 7 (g), column g: skip gives 1, use gives the 4 ways to spell "ba": 5.

Answer: 5.

The Solution Template

A full (m + 1) × (n + 1) table. Intermediate counts can exceed the final answer, so C++ and C use unsigned long long (wrap-around is defined there, and the answer is guaranteed to fit in 32 bits).

Distinct Subsequences Code
1var numDistinct = function(s, t) {
2 const m = s.length, n = t.length;
3 const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
4 for (let i = 0; i <= m; i++) dp[i][0] = 1;
5 for (let i = 1; i <= m; i++) {
6 for (let j = 1; j <= n; j++) {
7 dp[i][j] = dp[i - 1][j];
8 if (s[i - 1] === t[j - 1]) dp[i][j] += dp[i - 1][j - 1];
9 }
10 }
11 return dp[m][n];
12};

Edge Cases & Common Mistakes

  • t longer than s: the answer is 0; the table handles it without a special case.
  • Forgetting column 0: if dp[i][0] stays 0, every count stays 0.
  • Overflow: intermediate cells can be far bigger than the final answer. Use a 64-bit unsigned type in C and C++, where signed overflow is undefined.
  • One-row version updated left to right: dp[j - 1] is already overwritten with the new row; loop j from n down to 1.
  • Counting distinct strings instead of positions: the problem counts index choices, so "aaaa" contains "aa" 6 ways, not 1.

Summary

Set dp[i][0] = 1. Each cell is dp[i - 1][j] (skip s[i - 1]), plus dp[i - 1][j - 1] when s[i - 1] equals t[j - 1] (use it). Return dp[m][n]. O(m · n) time, and O(n) space with one row updated right to left.

More practice: DSA interview questions with answers · patterns cheat sheet

The approach, step by step

  1. Set column 0

    dp[i][0] = 1 for every i: the empty t is matched by picking nothing. The rest of row 0 is 0.

  2. Skip or use each letter

    dp[i][j] = dp[i - 1][j], plus dp[i - 1][j - 1] when s[i - 1] equals t[j - 1].

  3. Read the corner

    Return dp[m][n].

Frequently asked questions

What is the recurrence for Distinct Subsequences?

dp[i][j] counts the ways the first i letters of s contain the first j letters of t. The letter s[i - 1] can always be skipped, giving dp[i - 1][j]. If it equals t[j - 1] it can also be used as the last letter of t's prefix, adding dp[i - 1][j - 1].


Why is dp[i][0] equal to 1?

The empty string is a subsequence of every prefix of s in exactly one way: pick no letters. Every count in the table is built up from these 1s along the diagonals.


Can Distinct Subsequences use O(n) space?

Yes. Row i only reads row i - 1, so keep one array of length n + 1 and update it with j running from n down to 1: dp[j] += dp[j - 1] when s[i - 1] equals t[j - 1]. Going right to left keeps dp[j - 1] at its old value.

Applying for software engineering roles? Free, no sign-up:

AI resume checker — match your resume to the job description