leetcode --distinct subsequences

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 have
if(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];
        
    }
};

 

结果如下:

image

后来受了网上的启发,发现了一个可以省空间的做法,那就是循环利用,设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)。由于申请空间的次数减少,程序运行时间也减少了,如下:

image

posted @ 2013-08-13 13:25  曾见绝美的阳光  阅读(366)  评论(0编辑  收藏  举报