Distinct Subsequences

HardDynamic Programming · String DP🧩 String DPLeetCode #115 ↗Updated

Problem

Given two strings s and t, count in how many ways you can delete characters from s (possibly none) so that what remains is exactly t. Two ways differ if they keep a different set of positions of s. The answer is guaranteed to fit in a 32-bit signed integer.
Inputs = "rabbbit", t = "rabbit"Output3 (drop any one of the three b's)
Inputs = "babgbag", t = "bag"Output5

1 ≤ |s|, |t| ≤ 1000  ·  lowercase/uppercase English letters

Try it yourself

Java
class Solution {
    public int numDistinct(String s, String t) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Scan s one character at a time. If s[i] == t[j], every way of building t[0..j-1] so far can now be extended by this character, so ways[j+1] += ways[j]. Update j from right to left so each character of s is used at most once per subsequence.
PatternString DP

Instinct Two strings compared char by char (match, edit, interleave) means dp[i][j] over prefixes; one string's palindromes or splits means dp[l][r] or dp[i].

Memory hack Match? Go diagonal. Mismatch? Ask the neighbours. Up drops a char from s, left drops one from t, diagonal uses both.

Open the String DP guide →

Approaches

Brute ForceSkip-or-Match Recursion

Time O(2n)Space O(n)

At position i of s and j of t, we may always skip s[i]; if the characters are equal we may also match and advance both. Reaching the end of t counts as one way.

Java
public int numDistinct(String s, String t) {
    return count(s, t, 0, 0);
}

private int count(String s, String t, int i, int j) {
    if (j == t.length()) return 1;                      // all of t matched
    if (s.length() - i < t.length() - j) return 0;      // not enough characters left
    int ways = count(s, t, i + 1, j);                   // skip s[i]
    if (s.charAt(i) == t.charAt(j)) {
        ways += count(s, t, i + 1, j + 1);              // use s[i] for t[j]
    }
    return ways;
}

Mirrors the definition exactly, but the same (i, j) pairs are re-explored.

At a glance

LevelApproachTimeSpace
Brute ForceSkip-or-Match RecursionO(2n)O(n)
Better2-D Tabulation on PrefixesO(n · m)O(n · m)
Optimal1-D DP, Right-to-Left UpdateO(n · m)O(m)
Common pitfalls
  • Forgetting the base case ways[0] = 1 — every count stays 0.
  • Left-to-right inner loop in the 1-D version — reuses the same character of s.
  • Plain int cells — intermediate counts can exceed 231 even though the final answer fits.
Interview tips
  • Clarify that we count position sets, not distinct resulting strings (they are all equal to t).
  • Start from the skip/match recursion, then name the state (i, j) to justify memoization.
  • Draw the 2-D table for “babgbag”/“bag” and show it collapses into one row.
Pattern
Two-string DP → a state (i, j) over prefixes of both strings with skip/match transitions, compressible to one row.
My notes saved in this browser