How many times one string appears in another
Given strings s and t, return the number of distinct ways to choose positions of
s that spell t in order: the number of times t appears in s as a
subsequence. The empty string appears exactly once in any string.
Examples
| Input | Output | Why |
|---|---|---|
s = "rabbbit", t = "rabbit" | 3 | Leave out any one of the three b's. |
s = "babgbag", t = "bag" | 5 | Five choices of positions. |
s = "abc", t = "" | 1 | The empty string. |
Constraints
- 0 ≤ s.length, t.length ≤ 1000
- The answer fits in a 64-bit integer
Hints
Hint 1
dp[i][j] = number of ways the first j characters of t appear in the first i characters of s.
Hint 2
Always: dp[i − 1][j] (do not use s[i − 1]). If s[i − 1] = t[j − 1], also dp[i − 1][j − 1].
Hint 3
dp[i][0] = 1; dp[0][j] = 0 for j > 0.
Where people lose marks
- Running j upwards with one array, which uses the same character of s twice.
- dp[0][0] = 0: the empty t must count once.
- Overflow in 32-bit integers.
Dry run on "babgbag","bag"
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 22
Base cases filled first.
-
Step 2 / 22
'b' = 'b': skip it (0) or use it (1).
-
Step 3 / 22
'b' ≠ 'a': skip it, 0.
-
Step 4 / 22
'b' ≠ 'g': skip it, 0.
-
Step 5 / 22
'a' ≠ 'b': skip it, 1.
-
Step 6 / 22
'a' = 'a': skip it (0) or use it (1).
-
Step 7 / 22
'a' ≠ 'g': skip it, 0.
-
Step 8 / 22
'b' = 'b': skip it (1) or use it (1).
-
Step 9 / 22
'b' ≠ 'a': skip it, 1.
-
Step 10 / 22
'b' ≠ 'g': skip it, 0.
-
Step 11 / 22
'g' ≠ 'b': skip it, 2.
-
Step 12 / 22
'g' ≠ 'a': skip it, 1.
-
Step 13 / 22
'g' = 'g': skip it (0) or use it (1).
-
Step 14 / 22
'b' = 'b': skip it (2) or use it (1).
-
Step 15 / 22
'b' ≠ 'a': skip it, 1.
-
Step 16 / 22
'b' ≠ 'g': skip it, 1.
-
Step 17 / 22
'a' ≠ 'b': skip it, 3.
-
Step 18 / 22
'a' = 'a': skip it (1) or use it (3).
-
Step 19 / 22
'a' ≠ 'g': skip it, 1.
-
Step 20 / 22
'g' ≠ 'b': skip it, 3.
-
Step 21 / 22
'g' ≠ 'a': skip it, 4.
-
Step 22 / 22
'g' = 'g': skip it (1) or use it (4). The answer is 5.
How to think about it
A two-sequence table over prefixes. dp[i][j] counts the ways to spell t[:j] using positions of
s[:i]. The last character of s[:i] is either not used (dp[i − 1][j]) or, when it equals
the last character of t[:j], used for it (dp[i − 1][j − 1]). The two cases are disjoint, so their
counts add. One row per character of s suffices if j runs downwards, so that dp[j − 1] is still the previous
row's value.
Complexity. Time O(|s| · |t|) · Space O(|t|)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def num_distinct(s, t):
ways = [1] + [0] * len(t) # ways[j]: t[:j] inside the prefix of s seen so far
for ch in s:
for j in range(len(t), 0, -1): # downwards: ways[j - 1] is still the previous row
if t[j - 1] == ch:
ways[j] += ways[j - 1] # use this character of s for t[j - 1]
return ways[len(t)]#include <string>
#include <vector>
long long solve(std::string s, std::string t) {
std::vector<long long> ways(t.size() + 1, 0);
ways[0] = 1;
for (char ch : s)
for (int j = t.size(); j >= 1; --j) // downwards
if (t[j - 1] == ch) ways[j] += ways[j - 1];
return ways[t.size()];
}class Solution {
public long numDistinct(String s, String t) {
long[] ways = new long[t.length() + 1];
ways[0] = 1;
for (char ch : s.toCharArray())
for (int j = t.length(); j >= 1; j--) // downwards
if (t.charAt(j - 1) == ch) ways[j] += ways[j - 1];
return ways[t.length()];
}
}function numDistinct(s, t) {
const ways = [1, ...new Array(t.length).fill(0)];
for (const ch of s)
for (let j = t.length; j >= 1; j--) if (t[j - 1] === ch) ways[j] += ways[j - 1]; // downwards
return ways[t.length];
}fn solve(s: &str, t: &str) -> i64 {
let t: Vec<char> = t.chars().collect();
let mut ways = vec![0i64; t.len() + 1];
ways[0] = 1;
for ch in s.chars() {
for j in (1..=t.len()).rev() { // downwards
if t[j - 1] == ch {
ways[j] += ways[j - 1];
}
}
}
ways[t.len()]
}solve :: String -> String -> Int
solve s t = last (foldl step (1 : map (const 0) t) s)
where
-- the new row: ways[0] stays 1; ways[j] adds the old ways[j - 1] when t[j - 1] matches
step ways ch = head ways : zipWith3 (\c old prev -> if c == ch then old + prev else old) t (tail ways) ways
Variants to try
- Count modulo 10⁹ + 7 for longer strings.
- The number of distinct subsequences of s (counting each string once).
- Return one set of positions, or all of them for short inputs.
Next problems
Is a string an interleaving of two others?
Decide whether s3 can be built by merging s1 and s2, each kept in order. The state is how much of each has been used.
Open the problem → Hard30 minWildcard pattern matching
In the pattern, ? matches one character and * matches any run, including none. Does the pattern match the whole string?
Open the problem → Medium20 minThe shortest common supersequence
The shortest string that contains both strings as subsequences. Every shared character can be written once: n + m − LCS.
Open the problem →Write a solution and run it against the real test table.
Keep going
Is a string an interleaving of two others?
Decide whether s3 can be built by merging s1 and s2, each kept in order. The state is how much of each has been used.
Open the problem → Hard30 minWildcard pattern matching
In the pattern, ? matches one character and * matches any run, including none. Does the pattern match the whole string?
Open the problem → Medium20 minThe shortest common supersequence
The shortest string that contains both strings as subsequences. Every shared character can be written once: n + m − LCS.
Open the problem → Dynamic programmingThe pattern behind it
Write the recursion, notice the repeats, fill a table in an order that makes each entry final.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.