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.
Hint 1
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?
Hint 2
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?
Hint 3
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?
Hint 4
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.
Company Tags
Please upgrade to NeetCode Pro to view company tags.
Prerequisites
Before attempting this problem, you should be comfortable with:
Recursion - Building solutions by breaking problems into smaller subproblems with base cases
Dynamic Programming - Using memoization or tabulation to avoid redundant computations
Subsequences - Understanding the difference between subsequences (non-contiguous) and substrings (contiguous)
1. Recursion
Intuition
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:
skip the current character in s
use the current character, but only if it matches the current character in t
The 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.
Algorithm
If t is longer than s, return 0 immediately since it is impossible.
Define a recursive function dfs(i, j):
i is the current index in s
j is the current index in t
If j reaches the end of t:
Return 1 because a valid subsequence has been formed
If i reaches the end of s before t is finished:
Return 0 because no valid subsequence can be formed
Always consider skipping the current character in s:
res = dfs(i + 1, j)
If s[i] == t[j]:
Also consider using this character to match t[j]
Add dfs(i + 1, j + 1) to res
Return the total count res
Start the recursion from (0, 0) and return the final result
classSolution:defnumDistinct(self, s:str, t:str)->int:iflen(t)>len(s):return0defdfs(i, j):if j ==len(t):return1if i ==len(s):return0
res = dfs(i +1, j)if s[i]== t[j]:
res += dfs(i +1, j +1)return res
return dfs(0,0)
publicclassSolution{publicintnumDistinct(String s,String t){if(t.length()> s.length()){return0;}returndfs(s, t,0,0);}privateintdfs(String s,String t,int i,int j){if(j == t.length()){return1;}if(i == s.length()){return0;}int res =dfs(s, t, i +1, j);if(s.charAt(i)== t.charAt(j)){
res +=dfs(s, t, i +1, j +1);}return res;}}
classSolution{public:intnumDistinct(string s, string t){if(t.length()> s.length()){return0;}returndfs(s, t,0,0);}private:intdfs(const string &s,const string &t,int i,int j){if(j == t.length()){return1;}if(i == s.length()){return0;}int res =dfs(s, t, i +1, j);if(s[i]== t[j]){
res +=dfs(s, t, i +1, j +1);}return res;}};
classSolution{/**
* @param {string} s
* @param {string} t
* @return {number}
*/numDistinct(s, t){if(t.length > s.length){return0;}constdfs=(i, j)=>{if(j === t.length){return1;}if(i === s.length){return0;}let res =dfs(i +1, j);if(s[i]=== t[j]){
res +=dfs(i +1, j +1);}return res;};returndfs(0,0);}}
publicclassSolution{publicintNumDistinct(string s,string t){if(t.Length > s.Length){return0;}returnDfs(s, t,0,0);}privateintDfs(string s,string t,int i,int j){if(j == t.Length){return1;}if(i == s.Length){return0;}int res =Dfs(s, t, i +1, j);if(s[i]== t[j]){
res +=Dfs(s, t, i +1, j +1);}return res;}}
funcnumDistinct(s string, t string)int{iflen(t)>len(s){return0}var dfs func(i, j int)int
dfs =func(i, j int)int{if j ==len(t){return1}if i ==len(s){return0}
res :=dfs(i+1, j)if s[i]== t[j]{
res +=dfs(i+1, j+1)}return res
}returndfs(0,0)}
class Solution {funnumDistinct(s: String, t: String): Int {if(t.length > s.length)return0fundfs(i: Int, j: Int): Int {if(j == t.length)return1if(i == s.length)return0var res =dfs(i +1, j)if(s[i]== t[j]){
res +=dfs(i +1, j +1)}return res
}returndfs(0,0)}}
classSolution{funcnumDistinct(_ s:String,_ t:String)->Int{let sArray =Array(s), tArray =Array(t)let sLen = sArray.count, tLen = tArray.count
if tLen > sLen {return0}funcdfs(_ i:Int,_ j:Int)->Int{if j == tLen {return1}if i == sLen {return0}var res =dfs(i +1, j)if sArray[i]== tArray[j]{
res +=dfs(i +1, j +1)}return res
}returndfs(0,0)}}
Time & Space Complexity
Time complexity: O(2m)
Space complexity: O(m)
Where m is the length of the string s.
2. Dynamic Programming (Top-Down)
Intuition
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 s
j: the current index in t
The 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.
Algorithm
If the length of t is greater than the length of s, return 0 since it is impossible to form t.
Create a memoization map dp where:
the key is (i, j)
the value is the number of ways to form t[j:] from s[i:]
Define a recursive function dfs(i, j):
i represents the current position in s
j represents the current position in t
If j reaches the end of t:
Return 1 because a valid subsequence has been formed
If i reaches the end of s before t is fully matched:
Return 0
If the state (i, j) is already in dp:
Return the cached result
Always consider skipping the current character in s:
res = dfs(i + 1, j)
If s[i] matches t[j]:
Also consider using this character
Add dfs(i + 1, j + 1) to res
Store res in dp[(i, j)] and return it
Start the recursion from (0, 0) and return the final result
classSolution:defnumDistinct(self, s:str, t:str)->int:
m, n =len(s),len(t)
dp =[[0]*(n +1)for _ inrange(m +1)]for i inrange(m +1):
dp[i][n]=1for i inrange(m -1,-1,-1):for j inrange(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]
publicclassSolution{publicintnumDistinct(String s,String t){int m = s.length(), n = t.length();int[][] dp =newint[m +1][n +1];for(int i =0; i <= m; i++){
dp[i][n]=1;}for(int i = m -1; i >=0; i--){for(int j = n -1; j >=0; j--){
dp[i][j]= dp[i +1][j];if(s.charAt(i)== t.charAt(j)){
dp[i][j]+= dp[i +1][j +1];}}}return dp[0][0];}}
classSolution{public:intnumDistinct(string s, string t){int m = s.length(), n = t.length();
vector<vector<uint>>dp(m +1,vector<uint>(n +1,0));for(int i =0; i <= m; i++){
dp[i][n]=1;}for(int i = m -1; i >=0; i--){for(int j = n -1; j >=0; j--){
dp[i][j]= dp[i +1][j];if(s[i]== t[j]){
dp[i][j]+= dp[i +1][j +1];}}}return dp[0][0];}};
classSolution{/**
* @param {string} s
* @param {string} t
* @return {number}
*/numDistinct(s, t){let m = s.length,
n = t.length;let dp = Array.from({length: m +1},()=>Array(n +1).fill(0));for(let i =0; i <= m; i++){
dp[i][n]=1;}for(let i = m -1; i >=0; i--){for(let j = n -1; j >=0; j--){
dp[i][j]= dp[i +1][j];if(s[i]=== t[j]){
dp[i][j]+= dp[i +1][j +1];}}}return dp[0][0];}}
publicclassSolution{publicintNumDistinct(string s,string t){int m = s.Length, n = t.Length;int[,] dp =newint[m +1, n +1];for(int i =0; i <= m; i++){
dp[i, n]=1;}for(int i = m -1; i >=0; i--){for(int j = n -1; j >=0; j--){
dp[i, j]= dp[i +1, j];if(s[i]== t[j]){
dp[i, j]+= dp[i +1, j +1];}}}return dp[0,0];}}
funcnumDistinct(s string, t string)int{
m, n :=len(s),len(t)
dp :=make([][]int, m+1)for i :=range dp {
dp[i]=make([]int, n+1)}for i :=0; i <= m; i++{
dp[i][n]=1}for i := m -1; i >=0; i--{for j := n -1; j >=0; j--{
dp[i][j]= dp[i+1][j]if s[i]== t[j]{
dp[i][j]+= dp[i+1][j+1]}}}return dp[0][0]}
class Solution {funnumDistinct(s: String, t: String): Int {val m = s.length
val n = t.length
val dp =Array(m +1){IntArray(n +1)}for(i in0..m){
dp[i][n]=1}for(i in m -1 downTo 0){for(j in n -1 downTo 0){
dp[i][j]= dp[i +1][j]if(s[i]== t[j]){
dp[i][j]+= dp[i +1][j +1]}}}return dp[0][0]}}
classSolution{funcnumDistinct(_ s:String,_ t:String)->Int{let m = s.count, n = t.count
var dp =Array(
repeating:Array(repeating:0, count: n +1),
count: m +1)let sArray =Array(s)let tArray =Array(t)for i in0...m {
dp[i][n]=1}for i instride(from: m -1, through:0, by:-1){for j instride(from: n -1, through:0, by:-1){let base = dp[i +1][j]
dp[i][j]= base
if sArray[i]== tArray[j]{let addend = dp[i +1][j +1]if base >Int.max - addend {
dp[i][j]=0}else{
dp[i][j]+= addend
}}}}return dp[0][0]}}
Time & Space Complexity
Time complexity: O(m∗n)
Space complexity: O(m∗n)
Where m is the length of the string s and n is the length of the string t.
4. Dynamic Programming (Space Optimized)
Intuition
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)
we never need older rows once a new row is computed
This means we can optimize space by keeping only two 1D arrays:
one representing results for i + 1
one representing results for the current i
The key idea stays the same: At each position, we can either:
skip the current character in s
or use it if it matches the current character in t
Algorithm
Let m = len(s) and n = len(t).
Create two 1D arrays of size n + 1:
dp represents results for the next row (i + 1)
nextDp represents results for the current row (i)
Initialize the base case:
Set dp[n] = 1 and nextDp[n] = 1
There is exactly one way to form an empty t
Iterate over string s from right to left:
For each index i, iterate over string t from right to left:
Always carry over the value from skipping s[i]:
nextDp[j] = dp[j]
If s[i] == t[j]:
Add the number of ways from matching both characters:
nextDp[j] += dp[j + 1]
After finishing the row, copy nextDp into dp
After processing all characters, dp[0] contains the total number of distinct subsequences
publicclassSolution{publicintnumDistinct(String s,String t){int m = s.length(), n = t.length();int[] dp =newint[n +1];int[] nextDp =newint[n +1];
dp[n]= nextDp[n]=1;for(int i = m -1; i >=0; i--){for(int j = n -1; j >=0; j--){
nextDp[j]= dp[j];if(s.charAt(i)== t.charAt(j)){
nextDp[j]+= dp[j +1];}}
dp = nextDp.clone();}return dp[0];}}
classSolution{public:intnumDistinct(string s, string t){int m = s.size(), n = t.size();
vector<uint>dp(n +1,0);
vector<uint>nextDp(n +1,0);
dp[n]= nextDp[n]=1;for(int i = m -1; i >=0; i--){for(int j = n -1; j >=0; j--){
nextDp[j]= dp[j];if(s[i]== t[j]){
nextDp[j]+= dp[j +1];}}
dp = nextDp;}return dp[0];}};
classSolution{/**
* @param {string} s
* @param {string} t
* @return {number}
*/numDistinct(s, t){let m = s.length,
n = t.length;let dp =newArray(n +1).fill(0);let nextDp =newArray(n +1).fill(0);
dp[n]= nextDp[n]=1;for(let i = m -1; i >=0; i--){for(let j = n -1; j >=0; j--){
nextDp[j]= dp[j];if(s[i]=== t[j]){
nextDp[j]+= dp[j +1];}}
dp = nextDp.slice();}return dp[0];}}
publicclassSolution{publicintNumDistinct(string s,string t){int m = s.Length, n = t.Length;int[] dp =newint[n +1];int[] nextDp =newint[n +1];
dp[n]= nextDp[n]=1;for(int i = m -1; i >=0; i--){for(int j = n -1; j >=0; j--){
nextDp[j]= dp[j];if(s[i]== t[j]){
nextDp[j]+= dp[j +1];}}
dp =(int[])nextDp.Clone();}return dp[0];}}
funcnumDistinct(s string, t string)int{
m, n :=len(s),len(t)
dp :=make([]int, n+1)
nextDp :=make([]int, n+1)
dp[n]=1
nextDp[n]=1for i := m -1; i >=0; i--{for j := n -1; j >=0; j--{
nextDp[j]= dp[j]if s[i]== t[j]{
nextDp[j]+= dp[j+1]}}
dp =append([]int(nil), nextDp...)}return dp[0]}
class Solution {funnumDistinct(s: String, t: String): Int {val m = s.length
val n = t.length
var dp =IntArray(n +1)var nextDp =IntArray(n +1)
dp[n]=1
nextDp[n]=1for(i in m -1 downTo 0){for(j in n -1 downTo 0){
nextDp[j]= dp[j]if(s[i]== t[j]){
nextDp[j]+= dp[j +1]}}
dp = nextDp.copyOf()}return dp[0]}}
classSolution{funcnumDistinct(_ s:String,_ t:String)->Int{let m = s.count, n = t.count
var dp =[Int](repeating:0, count: n +1)var nextDp =[Int](repeating:0, count: n +1)
dp[n]=1
nextDp[n]=1let sArr =Array(s)let tArr =Array(t)for i instride(from: m -1, through:0, by:-1){for j instride(from: n -1, through:0, by:-1){
nextDp[j]= dp[j]if sArr[i]== tArr[j]{let addend = dp[j +1]if nextDp[j]>Int.max - addend {
nextDp[j]=0}else{
nextDp[j]+= addend
}}}
dp = nextDp
}return dp[0]}}
Time & Space Complexity
Time complexity: O(m∗n)
Space complexity: O(n)
Where m is the length of the string s and n is the length of the string t.
5. Dynamic Programming (Optimal)
Intuition
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:]
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]
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.
Algorithm
Let m = len(s) and n = len(t).
Create a 1D array dp of size n + 1:
dp[j] represents the number of ways to form t[j:] using the suffix of s currently being processed
Initialize the base case:
dp[n] = 1 because there is exactly one way to form an empty t (choose nothing)
Iterate through s from right to left (from m - 1 down to 0):
Set prev = 1 which corresponds to the diagonal value dp[i+1][n] (always 1)
For each character s[i], iterate through t from right to left (from n - 1 down to 0):
Start with res = dp[j] (skipping s[i])
If s[i] == t[j], add prev (matching both characters)
Update prev to the old dp[j] before overwriting it (so it becomes the next diagonal)
Write dp[j] = res
After processing all characters, dp[0] contains the total number of distinct subsequences of s that equal t
classSolution:defnumDistinct(self, s:str, t:str)->int:
m, n =len(s),len(t)
dp =[0]*(n +1)
dp[n]=1for i inrange(m -1,-1,-1):
prev =1for j inrange(n -1,-1,-1):
res = dp[j]if s[i]== t[j]:
res += prev
prev = dp[j]
dp[j]= res
return dp[0]
publicclassSolution{publicintnumDistinct(String s,String t){int m = s.length(), n = t.length();int[] dp =newint[n +1];
dp[n]=1;for(int i = m -1; i >=0; i--){int prev =1;for(int j = n -1; j >=0; j--){int res = dp[j];if(s.charAt(i)== t.charAt(j)){
res += prev;}
prev = dp[j];
dp[j]= res;}}return dp[0];}}
classSolution{public:intnumDistinct(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];}};
classSolution{/**
* @param {string} s
* @param {string} t
* @return {number}
*/numDistinct(s, t){let m = s.length,
n = t.length;let dp =newArray(n +1).fill(0);
dp[n]=1;for(let i = m -1; i >=0; i--){let prev =1;for(let j = n -1; j >=0; j--){let res = dp[j];if(s[i]=== t[j]){
res += prev;}
prev = dp[j];
dp[j]= res;}}return dp[0];}}
publicclassSolution{publicintNumDistinct(string s,string t){int m = s.Length, n = t.Length;int[] dp =newint[n +1];
dp[n]=1;for(int i = m -1; i >=0; i--){int prev =1;for(int j = n -1; j >=0; j--){int res = dp[j];if(s[i]== t[j]){
res += prev;}
prev = dp[j];
dp[j]= res;}}return dp[0];}}
funcnumDistinct(s string, t string)int{
m, n :=len(s),len(t)
dp :=make([]int, n+1)
dp[n]=1for i := m -1; i >=0; i--{
prev :=1for j := n -1; j >=0; j--{
res := dp[j]if s[i]== t[j]{
res += prev
}
prev = dp[j]
dp[j]= res
}}return dp[0]}
class Solution {funnumDistinct(s: String, t: String): Int {val m = s.length
val n = t.length
val dp =IntArray(n +1)
dp[n]=1for(i in m -1 downTo 0){var prev =1for(j in n -1 downTo 0){var res = dp[j]if(s[i]== t[j]){
res += prev
}
prev = dp[j]
dp[j]= res
}}return dp[0]}}
classSolution{funcnumDistinct(_ s:String,_ t:String)->Int{let m = s.count, n = t.count
var dp =[Int](repeating:0, count: n +1)
dp[n]=1let sArr =Array(s)let tArr =Array(t)for i instride(from: m -1, through:0, by:-1){var prev =1for j instride(from: n -1, through:0, by:-1){let base = dp[j]var res = base
if sArr[i]== tArr[j]{if base >Int.max - prev {
res =0}else{
res += prev
}}
prev = dp[j]
dp[j]= res
}}return dp[0]}}
Time & Space Complexity
Time complexity: O(m∗n)
Space complexity: O(n)
Where m is the length of the string s and n is the length of the string t.
Common Pitfalls
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.
# Wrong: Returns 0 when target is matchedif j ==len(t):return0# Correct: Successfully formed one subsequenceif j ==len(t):return1
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.
# Wrong: Only recurses on matchif s[i]== t[j]:return dfs(i +1, j +1)# Correct: Always skip, optionally match
res = dfs(i +1, j)# Always include skipif s[i]== t[j]:
res += dfs(i +1, j +1)# Add match option
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.