You are given two strings s and t, both consisting of english letters.
Return the number of distinct subsequences of s which are equal to t.
Example 1:
Input: s = "caaat", t = "cat"
Output: 3Explanation: There are 3 ways you can generate "cat" from s.
Example 2:
Input: s = "xxyxy", t = "xy"
Output: 5Explanation: There are 5 ways you can generate "xy" from s.
Constraints:
1 <= s.length, t.length <= 1000s and t consist of English letters.
You should aim for a solution as good or better than O(m * n) time and O(m * n) space, where m is the length of the string s and n is the length of the string t.
Try to think in terms of recursion and visualize it as a decision tree, as we need to explore all subsequences of s. Can you determine the possible decisions at each recursion step?
We recursively iterate through the strings using indices i and j for s and t, respectively. At each recursion step, we can either skip the current character of s or include it in the current path if it matches the current character of t. Can you determine the base conditions for this recursive function?
If index j goes out of bounds, we return 1 as a valid subsequence is found. If index i goes out of bounds first, we return 0. At each recursion step, we return the sum of both paths. This approach is exponential. Can you think of a way to optimize it?
We can use memoization to cache the results of recursive calls and avoid redundant computations. A hash map or a 2D array can be used to store these results.
This problem asks how many distinct subsequences of string s are equal to string t.
A subsequence is formed by deleting some characters from s without changing the order of the remaining characters.
At every position in s, we have a choice:
stThe recursive function represents:
“How many ways can we form t[j:] using characters from s[i:]?”
If we successfully match all characters of t, we have found one valid subsequence.
t is longer than s, return 0 immediately since it is impossible.dfs(i, j):i is the current index in sj is the current index in tj reaches the end of t:1 because a valid subsequence has been formedi reaches the end of s before t is finished:0 because no valid subsequence can be formeds:res = dfs(i + 1, j)s[i] == t[j]:t[j]dfs(i + 1, j + 1) to resres(0, 0) and return the final resultWhere is the length of the string .
This problem asks us to count how many distinct subsequences of string s are equal to string t.
The recursive solution works by trying all possibilities, but it repeats the same subproblems many times. To make it efficient, we use top-down dynamic programming (memoization).
A state is uniquely identified by:
i: the current index in sj: the current index in tThe recursive function answers the question:
“How many ways can we form t[j:] using characters from s[i:]?”
By storing the result for each (i, j) pair, we avoid recomputing the same state again and again.
t is greater than the length of s, return 0 since it is impossible to form t.dp where:(i, j)t[j:] from s[i:]dfs(i, j):i represents the current position in sj represents the current position in tj reaches the end of t:1 because a valid subsequence has been formedi reaches the end of s before t is fully matched:0(i, j) is already in dp:s:res = dfs(i + 1, j)s[i] matches t[j]:dfs(i + 1, j + 1) to resres in dp[(i, j)] and return it(0, 0) and return the final resultclass Solution:
def numDistinct(self, s: str, t: str) -> int:
if len(t) > len(s):
return 0
dp = {}
def dfs(i, j):
if j == len(t):
return 1
if i == len(s):
return 0
if (i, j) in dp:
return dp[(i, j)]
res = dfs(i + 1, j)
if s[i] == t[j]:
res += dfs(i + 1, j + 1)
dp[(i, j)] = res
return res
return dfs(0, 0)Where is the length of the string and is the length of the string .
We want to count how many distinct subsequences of string s are equal to string t.
A subsequence is formed by deleting characters from s without changing the order.
So for every character in s, we have a choice:
t)Instead of solving this recursively, we use bottom-up dynamic programming by building answers for smaller suffixes first.
We define a DP state that answers:
“How many ways can we form t[j:] using s[i:]?”
By filling a DP table from the end of the strings toward the beginning, we ensure that all required subproblems are already solved.
m = len(s) and n = len(t).dp of size (m + 1) x (n + 1):dp[i][j] represents the number of ways to form t[j:] from s[i:]i, set dp[i][n] = 1t (by choosing nothing)(i, j):s[i]:dp[i][j] = dp[i + 1][j]s[i] == t[j]:s[i] to match t[j]dp[i + 1][j + 1] to dp[i][j]dp[0][0] contains the total number of distinct subsequencesdp[0][0]class Solution:
def numDistinct(self, s: str, t: str) -> int:
m, n = len(s), len(t)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
dp[i][n] = 1
for i in range(m - 1, -1, -1):
for j in range(n - 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]Where is the length of the string and is the length of the string .
We want to count how many distinct subsequences of string s are equal to string t.
From the bottom-up DP approach, we know that:
dp[i][j] depends only on values from the next row (i + 1)This means we can optimize space by keeping only two 1D arrays:
i + 1iThe key idea stays the same:
At each position, we can either:
stm = len(s) and n = len(t).n + 1:dp represents results for the next row (i + 1)nextDp represents results for the current row (i)dp[n] = 1 and nextDp[n] = 1ts from right to left:i, iterate over string t from right to left:s[i]:nextDp[j] = dp[j]s[i] == t[j]:nextDp[j] += dp[j + 1]nextDp into dpdp[0] contains the total number of distinct subsequencesdp[0]class Solution:
def numDistinct(self, s: str, t: str) -> int:
m, n = len(s), len(t)
dp = [0] * (n + 1)
nextDp = [0] * (n + 1)
dp[n] = nextDp[n] = 1
for i in range(m - 1, -1, -1):
for j in range(n - 1, -1, -1):
nextDp[j] = dp[j]
if s[i] == t[j]:
nextDp[j] += dp[j + 1]
dp = nextDp[:]
return dp[0]Where is the length of the string and is the length of the string .
We want to count how many distinct subsequences of s equal t.
From the classic DP idea:
dp[i][j] = number of ways to form t[j:] using s[i:]s[i] -> dp[i+1][j]s[i] == t[j], we can also match them -> dp[i+1][j+1]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.
m = len(s) and n = len(t).dp of size n + 1:dp[j] represents the number of ways to form t[j:] using the suffix of s currently being processeddp[n] = 1 because there is exactly one way to form an empty t (choose nothing)s from right to left (from m - 1 down to 0):prev = 1 which corresponds to the diagonal value dp[i+1][n] (always 1)s[i], iterate through t from right to left (from n - 1 down to 0):res = dp[j] (skipping s[i])s[i] == t[j], add prev (matching both characters)prev to the old dp[j] before overwriting it (so it becomes the next diagonal)dp[j] = resdp[0] contains the total number of distinct subsequences of s that equal tdp[0]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]Where is the length of the string and is the length of the string .
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.
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.
# Wrong: Returns 0 when target is matched
if j == len(t):
return 0
# Correct: Successfully formed one subsequence
if j == len(t):
return 1At 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.
# Wrong: Only recurses on match
if s[i] == t[j]:
return dfs(i + 1, j + 1)
# Correct: Always skip, optionally match
res = dfs(i + 1, j) # Always include skip
if s[i] == t[j]:
res += dfs(i + 1, j + 1) # Add match optionThe 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.