https://leetcode.com/problems/distinct-subsequences/
给定两个字符串 s s s和 t t t,问 t t t作为子序列在 s s s中出现了多少次。
法1:动态规划。设 f [ i ] [ j ] f[i][j] f[i][j]是 s [ 0 : i ] s[0:i] s[0:i]中 t [ 0 : j ] t[0:j] t[0:j]作为子序列出现了多少次。那么,如果 s [ i ] ≠ t [ j ] s[i]\ne t[j] s[i]=t[j],则子序列 t [ 0 : j ] t[0:j] t[0:j]只能是在 s [ 0 : i − 1 ] s[0:i-1] s[0:i−1]里出现,所以 f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i−1][j];如果 s [ i ] = t [ j ] s[i]=t[j] s[i]=t[j],则除了 f [ i − 1 ] [ j ] f[i-1][j] f[i−1][j]之外, s [ i ] s[i] s[i]可以与 t [ j ] t[j] t[j]进行配对,再加上 s [ 0 : i − 1 ] s[0:i-1] s[0:i−1]含 t [ 0 : j − 1 ] t[0:j-1] t[0:j−1]作为子序列,合起来就能拼成 t [ 0 : j ] t[0:j] t[0:j],所以此时 f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − 1 ] f[i][j]=f[i-1][j]+f[i-1][j-1] f[i][j]=f[i−1][j]+f[i−1][j−1]。综上有: f [ i ] [ j ] = { f [ i − 1 ] [ j ] , s [ i ] ≠ t [ j ] f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − 1 ] , s [ i ] = t [ j ] f[i][j]=\begin{cases}f[i-1][j], s[i]\ne t[j]\\ f[i-1][j]+f[i-1][j-1], s[i]=t[j]\end{cases} f[i][j]={ f[i−1][j],s[i]=t[j]f[i−1][j]+f[i−1][j−1],s[i]=t[j]边界条件,如果 s [ 0 ] = t [ 0 ] s[0]=t[0] s[0]=t[0]的话,则有 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1,否则 f [ 0 ] [ 0 ] = 0 f[0][0]=0 f[0][0]=0,而 f [ i ] [ 0 ] f[i][0] f[i][0]实际上就是 s [ 0 : i ] s[0:i] s[0:i]里字符 t [ 0 ] t[0] t[0]出现了多少次。容易看出 ∀ j > 0 , f [ 0 ] [ j ] = 0 \forall j > 0,f[0][j]=0 ∀j>0,f[0][j]=0。最后要返回的就是 f [ l s − 1 ] [ l t − 1 ] f[l_s-1][l_t-1] f[ls−1][lt−1]。代码如下:
public class Solution {
public int numDistinct(String s, String t) {
// 这里要特判一下空串
if (s.isEmpty()) {
return t.isEmpty() ? 1 : 0;
}
int[][] dp = new int[s.length()][t.length()];
dp[0][0] = s.charAt(0) == t.charAt(0) ? 1 : 0;
for (int i = 1; i < s.length(); i++) {
dp[i][0] = dp[i - 1][0] + (s.charAt(i) == t.charAt(0) ? 1 : 0);
}
for (int i = 1; i < s.length(); i++) {
for (int j = 1; j < t.length(); 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[s.length() - 1][t.length() - 1];
}
}
时空复杂度 O ( l s l t ) O(l_sl_t) O(lslt)。
法2:记忆化搜索。设 f [ i ] [ j ] f[i][j] f[i][j]是 s [ i : ] s[i:] s[i:]包含 t [ j : ] t[j:] t[j:]子序列数目,则有: f [ i ] [ j ] = { f [ i + 1 ] [ j ] , s [ i ] ≠ t [ j ] f [ i + 1 ] [ j ] + f [ i + 1 ] [ j + 1 ] , s [ i ] = t [ j ] f[i][j]=\begin{cases}f[i+1][j],s[i]\ne t[j]\\ f[i+1][j]+f[i+1][j+1], s[i]=t[j]\end{cases} f[i][j]={ f[i+1][j],s[i]=t[j]f[i+1][j]+f[i+1][j+1],s[i]=t[j]递归出口是 f [ k ] [ l t ] f[k][l_t] f[k][lt],这是 s [ k : ] s[k:] s[k:]含多少个空串子序列,其实就是 1 1 1。代码如下:
import java.util.Arrays;
public class Solution {
public int numDistinct(String s, String t) {
int[][] dp = new int[s.length()][t.length()];
for (int[] row : dp) {
Arrays.fill(row, -1);
}
return dfs(0, 0, s, t, dp);
}
private int dfs(int i, int j, String s, String t, int[][] dp) {
// 如果后面长度不够了,则直接返回0
if (s.length() - i < t.length() - j) {
return 0;
}
// j走完t了,返回1(相当于s[i : ]含空串子序列数目,是1)
if (j == t.length()) {
return 1;
}
// 如果有记忆则调取记忆
if (dp[i][j] != -1) {
return dp[i][j];
}
int res = 0;
for (int k = i; k < s.length(); k++) {
res = dfs(i + 1, j, s, t, dp);
if (s.charAt(i) == t.charAt(j)) {
res += dfs(i + 1, j + 1, s, t, dp);
}
}
// 返回之前存一下记忆
dp[i][j] = res;
return res;
}
}
时空复杂度一样。