1.题目描述
Given a string S and a string T, count the number of distinct subsequences of T in S.A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, "ACE" is a subsequence of "ABCDE" while "AEC" is not).Here is an example:S = "rabbbit", T = "rabbit"Return 3.
2.解题思路
这道题一看就感觉能用动态规划解,但是想了好久还是没有想到一个动态规划的最优子结构,没办法, 只好在discuss里面找,总算看明白了递推式:
DP: let F(i, j) denote the number of ways for S[0]…S[i] contain T[0]..T[j], Then F(n-1, m-1) is our answer and we haveif(S[i] != T[j]) F(i, j) = F(i-1, j);if(S[i] == T[j]) F(i, j) = F(i-1, j-1) + F(i-1, j);自以为理解了,然后写出了下面这个动态规划的算法,写的过程就觉得繁冗拖沓,不太对劲,果然,写完了小数据及可以通过,但是大数据集过不了(TLE),仔细分析发现自己的算法是O(N2)的,空间效率是O(MN),是个很不好的代码
class Solution {
public:
int numDistinct(string S, string T) {
// Start typing your C/C++ solution below
// DO NOT write int main() function
if(S.empty()||T.empty())return 0;vector<vector<int> > dp;
vector<int>cur;
cur.assign(T.size(),0);dp.push_back(cur);if(S[0]==T[0])dp[0][0]=1;
for(int i = 1;i<S.size();++i){vector<int> dpi;
dpi.assign(T.size(),0);for(int k = 0;k<=i;++k){if(S[k]==T[0])dpi[0]=dpi[0]+1;
}for(int j = 1;j<T.size();++j){dpi[j] = dp[i-1][j] +((S[i]==T[j])?dp[i-1][j-1]:0);}dp.push_back(dpi);}return dp[S.size()-1][T.size()-1];
}};查看上述代码很容易发现,代码时间复杂度瓶颈在于求dp[i][0]的代价。实际上,我考虑不周,不需要这么每次都遍历一下。稍微修改一下代码即可,如下,时间复杂度变成了O(MN):
class Solution {
public:
int numDistinct(string S, string T) {
// Start typing your C/C++ solution below
// DO NOT write int main() function
if(S.empty()||T.empty())return 0;vector<vector<int> > dp;
vector<int>cur;
cur.assign(T.size(),0);dp.push_back(cur);if(S[0]==T[0])dp[0][0]=1;
for(int i = 1;i<S.size();++i){vector<int> dpi;
dpi.assign(T.size(),0);dpi[0]=dp[i-1][0]+(S[i]==T[0]);for(int j = 1;j<T.size();++j){dpi[j] = dp[i-1][j] +((S[i]==T[j])?dp[i-1][j-1]:0);}dp.push_back(dpi);}return dp[S.size()-1][T.size()-1];
}};结果如下:
后来受了网上的启发,发现了一个可以省空间的做法,那就是循环利用,设F[i][j]表示S[0…i]中包含T[0…j]的总个数,如果求得了F[i]中每一个元素,那么F[i+1]是可以递推出来的,递推公式在上面已经写过,对于F[i+1]中的每一个元素F[i+1][j],它只跟F[i][j]以及F[i][j-1]有关,如此这般,我们就可以循环利用F,于是,若我们在第i轮遍历求的了Fi,那么Fi+1可以逐步求出来,由于每一步Fi[j]会被替换,所以需要保存下来。
class Solution {
public:
int numDistinct(string S, string T) {
// Start typing your C/C++ solution below
// DO NOT write int main() function
vector<int> F;
F.assign(T.size(),0);if(S[0]==T[0])F[0] =1;
for(int i = 1;i<S.size();++i){int pre=F[0];
F[0] = F[0]+(S[i]==T[0]);int cur;
for(int j =1;j<T.size();++j){cur = F[j];F[j] = F[j]+(S[i]==T[j])*pre;pre = cur;}}return F[T.size()-1];
}};于是,空间需求降到了O(M)。由于申请空间的次数减少,程序运行时间也减少了,如下: