Problem Statement: Distinct Subsequences
Given two strings s
and t
, return the number of distinct subsequences of s
that equals t
. A subsequence of a string is a new string formed from the original string by deleting some (or no) characters without changing the relative order of the remaining characters.
For example, if s = "rabbbit"
and t = "rabbit"
, the number of distinct subsequences is 3.
Approach
This problem can be solved using Dynamic Programming (DP). The idea is to maintain a 2D table dp[i][j]
where:
i
represents the firsti
characters ofs
.j
represents the firstj
characters oft
.
dp[i][j]
stores the number of ways to form the first j
characters of t
from the first i
characters of s
.
Recurrence Relation:
- If
s[i-1] == t[j-1]
, we have two options:- Include
s[i-1]
as part of the subsequence formingt[j-1]
, or - Exclude it.
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
- Include
- If
s[i-1] != t[j-1]
, we can only excludes[i-1]
from the subsequence:cssCopy codedp[i][j] = dp[i-1][j]
Base Case:
dp[0][0] = 1
(An empty string can match another empty string).dp[i][0] = 1
for alli
(An empty stringt
can be formed from any prefix ofs
by deleting all characters).dp[0][j] = 0
for allj > 0
(An empty strings
cannot form any non-empty stringt
).
Time Complexity:
- The time complexity is O(m * n), where
m
is the length of strings
andn
is the length of stringt
.
Space Complexity:
- The space complexity is O(m * n), where
m
is the length of strings
andn
is the length of stringt
.
Code in Multiple Languages
C:
#include <stdio.h>
#include <string.h>
int numDistinct(char* s, char* t) {
int m = strlen(s);
int n = strlen(t);
int dp[m + 1][n + 1];
// Base case initialization
for (int i = 0; i <= m; i++) {
dp[i][0] = 1; // An empty string t can be formed from any prefix of s
}
for (int j = 1; j <= n; j++) {
dp[0][j] = 0; // An empty string s cannot form a non-empty t
}
// Fill the dp table
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s[i - 1] == t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[m][n];
}
int main() {
char s[] = "rabbbit";
char t[] = "rabbit";
printf("%d\n", numDistinct(s, t)); // Output: 3
return 0;
}
C++:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int numDistinct(string s, string t) {
int m = s.size(), n = t.size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 0; i <= m; i++) {
dp[i][0] = 1; // An empty t can be formed from any prefix of s
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s[i - 1] == t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[m][n];
}
int main() {
string s = "rabbbit", t = "rabbit";
cout << numDistinct(s, t) << endl; // Output: 3
return 0;
}
Java:
public class DistinctSubsequences {
public static int numDistinct(String s, String t) {
int m = s.length(), n = t.length();
int[][] dp = new int[m + 1][n + 1];
// Initialize base case
for (int i = 0; i <= m; i++) {
dp[i][0] = 1; // An empty string t can be formed from any prefix of s
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s.charAt(i - 1) == t.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[m][n];
}
public static void main(String[] args) {
String s = "rabbbit", t = "rabbit";
System.out.println(numDistinct(s, t)); // Output: 3
}
}
Python:
def numDistinct(s: str, t: str) -> int:
m, n = len(s), len(t)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# Base case
for i in range(m + 1):
dp[i][0] = 1 # An empty t can be formed from any prefix of s
for i in range(1, m + 1):
for j in range(1, n + 1):
if s[i - 1] == t[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
else:
dp[i][j] = dp[i - 1][j]
return dp[m][n]
# Example usage
s = "rabbbit"
t = "rabbit"
print(numDistinct(s, t)) # Output: 3
C#:
using System;
class Program {
public static int NumDistinct(string s, string t) {
int m = s.Length, n = t.Length;
int[,] dp = new int[m + 1, n + 1];
// Initialize base case
for (int i = 0; i <= m; i++) {
dp[i, 0] = 1; // An empty t can be formed from any prefix of s
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s[i - 1] == t[j - 1]) {
dp[i, j] = dp[i - 1, j - 1] + dp[i - 1, j];
} else {
dp[i, j] = dp[i - 1, j];
}
}
}
return dp[m, n];
}
static void Main() {
string s = "rabbbit";
string t = "rabbit";
Console.WriteLine(NumDistinct(s, t)); // Output: 3
}
}
JavaScript:
function numDistinct(s, t) {
const m = s.length, n = t.length;
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
// Base case
for (let i = 0; i <= m; i++) {
dp[i][0] = 1; // An empty t can be formed from any prefix of s
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (s[i - 1] === t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[m][n];
}
// Example usage
const s = "rabbbit", t = "rabbit";
console.log(numDistinct(s, t)); // Output: 3
Summary:
This solution uses Dynamic Programming to efficiently solve the problem of finding distinct subsequences. The idea is to build a 2D table dp
that records how many ways the string t
can be formed from any prefix of string s
. Each of the implementations in C, C++, Java, Python, C#, and JavaScript follows the same approach with appropriate syntax for the respective language.