Distinct Subsequences — Solution in Python, Java & C++
Distinct Subsequences solution explained: step-by-step approach, complexity and code in Python, Java, C++ and JavaScript. A hard problem.
Distinct Subsequences is a hard coding problem on strings and dynamic programming, asked in Amazon, Google and Bloomberg interviews.
- Difficulty: Hard
- Topics: Strings, Dynamic Programming
- Asked at: Amazon, Google, Bloomberg
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
Given two strings s and t, return the number of distinct subsequences of s that equal t.
Subsequences occurring at different index sets count separately.
Example 1
Input: s = "rabbbit", t = "rabbit"
Output: 3
Explanation: Any one of the three b's can be the one dropped.
Example 2
Input: s = "babgbag", t = "bag"
Output: 5
Example 3
Input: s = "abc", t = "abcd"
Output: 0
Constraints
1 <= s.length, t.length <= 1000Both consist of English letters.The answer fits in a 32-bit signed integer.
Hints for Distinct Subsequences
Hint 1
dp[i][j]counts the ways to buildt[0..j)froms[0..i).Hint 2
You may always skip
s[i-1]; when it matchest[j-1]you may also consume it.Hint 3
Rolling the table down to one row works if you iterate
jdownwards.
How to solve Distinct Subsequences
Count matchings rather than searching for them. Each character of s either participates in the match or is skipped, and the two options add.
Approach
- Let
dp[j]be the number of ways to formt[0..j)using the prefix ofsprocessed so far; start withdp[0] = 1. - For each character of
s, sweepjfrommdown to 1. - When
s[i-1] == t[j-1], adddp[j-1]todp[j]— consuming this character to extend a shorter match. - Return
dp[m].
Why it works
dp[j] before the update already counts matches that skip s[i-1], so adding dp[j-1] folds in the matches that use it. Sweeping j downwards guarantees dp[j-1] still holds the previous row's value, which is what the recurrence needs.
Complexity
- Time —
O(n · m) - Space —
O(m)
Pitfalls
- Sweeping
jupwards uses the already-updateddp[j-1]and counts the same character twice. dp[0] = 1is essential: there is exactly one way to form the empty string.- Recursing without memoisation is exponential on inputs like
"babgbag"scaled up.
Reference solution
Python
def numDistinct(s: str, t: str) -> int:
m = len(t)
dp = [0] * (m + 1)
dp[0] = 1
for ch in s:
for j in range(m, 0, -1):
if ch == t[j - 1]:
dp[j] += dp[j - 1]
return dp[m]Java
public static int numDistinct(String s, String t) {
int m = t.length();
long[] dp = new long[m + 1];
dp[0] = 1;
for (int i = 0; i < s.length(); i++) {
for (int j = m; j >= 1; j--) {
if (s.charAt(i) == t.charAt(j - 1)) dp[j] += dp[j - 1];
}
}
return (int) dp[m];
}C++
int numDistinct(string s, string t) {
int m = (int) t.size();
vector<long long> dp(m + 1, 0);
dp[0] = 1;
for (int i = 0; i < (int) s.size(); i++) {
for (int j = m; j >= 1; j--) {
if (s[i] == t[j - 1]) dp[j] += dp[j - 1];
}
}
return (int) dp[m];
}JavaScript
var numDistinct = function(s, t) {
const m = t.length;
const dp = new Array(m + 1).fill(0);
dp[0] = 1;
for (let i = 0; i < s.length; i++) {
for (let j = m; j >= 1; j--) {
if (s.charAt(i) === t.charAt(j - 1)) dp[j] += dp[j - 1];
}
}
return dp[m];
};Also on the editorial tab: C, C#, Go, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.
All 515 strings problems · the whole catalogue
Learn the technique: Strings · Dynamic Programming