◀ THE GRIND — 2-D DYNAMIC PROGRAMMING

Distinct Subsequences

The drill: Count the distinct ways to obtain a shorter string by deleting characters from a longer one, without reordering what's left — every deletion pattern that lands exactly on the target counts once.

THE BRIEFING — THE FULL DRILL, IN MY OWN WORDS

Two strings arrive: a longer source and a shorter target. The task is counting how many distinct ways letters can be deleted from the source — without disturbing the order of what's left — so the remainder reads exactly like the target.

Two deletion patterns count separately whenever they keep characters from different positions in the source, even when the surviving text looks identical. A repeated letter at several source positions can each anchor its own path to the same match.

Totals can climb fast when the source repeats letters heavily, since many independent deletion paths can land on the same target text — the count of paths is what gets reported, not any single path.

EX 01
s = "applesauce" · t = "ale"
2
THE BOARD'S EXAMPLE
EX 02
s = "" · t = ""
1
TWO EMPTY STRINGS — ONE TRIVIAL MATCH
EX 03
s = "hello" · t = ""
1
EMPTY TARGET ALWAYS MATCHES ONCE, BY DELETING EVERYTHING
THE HINTS — TAKE ONLY WHAT YOU NEED
HINT 1 THE NUDGE

At the first character of the longer string you always have two options — use it or skip it — and using it is only legal when it currently matches the next needed character of the target. Where does the branching actually come from?

HINT 2 THE STRUCTURE

Recurse on a pair of positions, one per string. Skipping never moves the target position; matching (when the characters agree) advances both. What happens once the target position reaches the end of the target?

HINT 3 ONE STEP FROM THE ANSWER

dp(i, j) = dp(i+1, j) [skip] plus dp(i+1, j+1) when s[i] == t[j] [use it]. Base cases: dp(i, len(t)) = 1 for any i — the target is already fully matched — and dp(len(s), j) = 0 whenever j < len(t).

COACH'S BOARD — THE PATTERN, STEP BY STEP
COUNTING THE DELETIONSPATTERN · MEMO ON (i, j)s = "bab" · t = "ab"
1
1
1
1
STEP 1

s='bab', t='ab'. dp[i][j] counts ways to match t[j:] using s[i:]. Base column j=2 (target already empty) is1 for every row.

STEP 1 / 5 · ← → WORK TOO
THE SPLITS — TWO PACES, TWO LANGUAGES
grind/distinct-subsequences.pyRACE PACE
LANG ▸
PACE ▸
class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        n, m = len(s), len(t)
        # dp[i][j] = ways to match t[j:] using s[i:]
        dp = [[0] * (m + 1) for _ in range(n + 1)]
        for i in range(n + 1):
            dp[i][m] = 1

        for i in range(n - 1, -1, -1):
            for j in range(m - 1, -1, -1):
                dp[i][j] = dp[i + 1][j]
                if s[i] == t[j]:
                    dp[i][j] += dp[i + 1][j + 1]

        return dp[0][0]
TIME O(M·N)SPACE O(M·N)PYTHON · RACE PACE · 15 LN

✓ CHIP-TIMED — ALL 4 SOLUTIONS RAN GREEN AGAINST SELF-AUTHORED CASES IN CI · JDK 21 · CPYTHON 3.12 · NOTHING PUBLISHES RED