Distinct Subsequences
Hard · 2-D Dynamic Programming · Explanation and code example
Dynamic Programming (Optimal)
How it works
We want to count how many distinct subsequences of s equal t.
From the classic DP idea:
dp[i][j]= number of ways to formt[j:]usings[i:]- Transition:
- always can skip
s[i]->dp[i+1][j] - if
s[i] == t[j], we can also match them ->dp[i+1][j+1]
- always can skip
The space-optimized version uses a 1D array where dp[j] represents the values from the "next row" (i+1).
But when updating dp[j] in-place, we still need access to the old value of dp[j+1] (which corresponds to dp[i+1][j+1]).
To solve this without an extra array, we carry that needed diagonal value using a single variable (prev), which always holds the correct "old dp[j+1]" for the current update.
Steps
- Let
m = len(s)andn = len(t). - Create a 1D array
dpof sizen + 1:dp[j]represents the number of ways to formt[j:]using the suffix ofscurrently being processed
- Initialize the base case:
dp[n] = 1because there is exactly one way to form an emptyt(choose nothing)
- Iterate through
sfrom right to left (fromm - 1down to0):- Set
prev = 1which corresponds to the diagonal valuedp[i+1][n](always1)
- Set
- For each character
s[i], iterate throughtfrom right to left (fromn - 1down to0):- Start with
res = dp[j](skippings[i]) - If
s[i] == t[j], addprev(matching both characters) - Update
prevto the olddp[j]before overwriting it (so it becomes the next diagonal) - Write
dp[j] = res
- Start with
- After processing all characters,
dp[0]contains the total number of distinct subsequences ofsthat equalt - Return
dp[0]
Complexity
- Time complexity:
O(m * n) - Space complexity:
O(n)
Where
mis the length of the stringsandnis the length of the stringt.
Things to watch for
Confusing Subsequence with Substring
A subsequence allows skipping characters while maintaining order, whereas a substring requires consecutive characters. This problem counts subsequences, so you must consider both skipping and using each character in s, not just sliding windows of consecutive characters.
Incorrect Base Case for Empty Target
When the target string t is fully matched (j == len(t)), the function should return 1 (one valid way found), not 0. Returning 0 here would cause all paths to report no valid subsequences.
Forgetting to Always Include the Skip Option
At each position in s, you must always consider skipping the current character, regardless of whether it matches t[j]. A common mistake is only recursing when characters match, which misses valid subsequences that use later occurrences of the same character.
Integer Overflow in Large Inputs
The number of distinct subsequences can grow exponentially. In languages with fixed-size integers, the result may overflow. Some implementations use unsigned integers or modular arithmetic to handle this, depending on the problem constraints.
Python code example
class Solution:
def numDistinct(self, s: str, t: str) -> int:
m, n = len(s), len(t)
dp = [0] * (n + 1)
dp[n] = 1
for i in range(m - 1, -1, -1):
prev = 1
for j in range(n - 1, -1, -1):
res = dp[j]
if s[i] == t[j]:
res += prev
prev = dp[j]
dp[j] = res
return dp[0]
C++ code example
class Solution {
public:
int numDistinct(string s, string t) {
int m = s.size(), n = t.size();
vector<uint> dp(n + 1, 0);
dp[n] = 1;
for (int i = m - 1; i >= 0; i--) {
int prev = 1;
for (int j = n - 1; j >= 0; j--) {
uint res = dp[j];
if (s[i] == t[j]) {
res += prev;
}
prev = dp[j];
dp[j] = res;
}
}
return dp[0];
}
};
Source and license
Adapted from NeetCode’s source · MIT license. Node types are supplied by LeetCode where needed.