Distinct Subsequences

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.

这题是求在S中有多少T的不同子串。这是一道典型的双序列动态规划问题。重点在于如何定义状态(也就是最优解的结构特征,一般表达形式)。这一题如果直接想拿整体的T在S中查找,无从下手。所以使用双序列问题的一般状态形式,即求子串一的第i 个和子串二的第j个关系。在这里f[i][j]是S的前i个字符中包含多少个不同子串是T的前j个字符。

之后是转移方程。首先这种字符串的题目没有明确的可以跳跃的地方,所以考虑f[i][j]如何从[i-1][j-1],f[i][j-1],f[i-1][j]转换过来。首先是f[i][j-1]是无法转换到f[i][j]的因为无法搭建j-1到j的一个关系(因为本身是不连续的)。之后只剩下f[i-1][j-1]和f[i-1][j]。可以看到当S的第i个字符不等于T的第j个字符时(S[i-1]!=T[j-1]),这个多出来的字符没有用,f[i][j] = f[i-1][j]。而当S[i-1]==T[j-1]时,如果S的前i-1个字符中子串已经有T的前j-1个字符,则这i-1个字符和S的第i 个字符一下子可以组成同样个数的T的前j个字符。这些都是以S[i-1]结尾的,如果S的前i-1个字符中子串已经有T的前j个字符,这些又可以加进去(肯定是不同的子串,结尾的位置不一样)。

状态:f[i][j] S的前i 个字符中有多少distinct subsequences是T的前j个字符。

转移方程: f[i][j] = f[i-1][j] (s[i-1]!=t[j-1])

               f[i][j] = f[i-1][j-1]+f[i-1][j](s[i-1]==t[j-1])

初始状态和计算方向: f[i][0] =1,f[0][j]=0(j>0) 从上到下,从左到右

最终答案为:f[m][n] 

直接按照这种思路写出来的代码时间和空间都为O(mn)的解法如下,注意f[i][j]=0(i<j):

class Solution(object):
    def numDistinct(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: int
        """
        if not t: 
            return 1
        if not s:
            return 0
        l1 = len(s)
        l2 = len(t)
        if l1 < l2:
            return 0
        res = [[0 for i in xrange(l2+1)] for j in xrange(l1+1)]
        for i in xrange(l1+1):
            res[i][0] = 1
            
        for j in xrange(1,l2+1):
            for i in xrange(j,l1+1):
                if s[i-1] != t[j-1]:
                    res[i][j] = res[i-1][j]
                else:
                    res[i][j] = res[i-1][j-1] + res[i-1][j]
        return res[l1][l2]

当然和其他二维DP一样,这种解法也是可以进行空间优化的,可以使用滚动数组来一行一行处理。f[i-1][j-1]可以使用之前在Edit Distance中的策略使用一个pre来保存 f[i-1][j-1]。和Edit Distance那题不一样的是,这题不需要f[i][j-1]。所以完全可以从右往左计算,不需要使用pre保存中间结果。要快很多。

从右往左的代码:

class Solution(object):
    def numDistinct(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: int
        """
        if not t: 
            return 1
        if not s:
            return 0
        res = [0 for i in xrange(len(t)+1)]
        res[0] = 1  
        
        for i in xrange(1,len(s)+1):
            for j in xrange(len(t),0,-1):
                if s[i-1] == t[j-1]:
                    res[j] += res[j-1]
        return res[-1]

pre的代码,这种要慢一些:

class Solution(object):
    def numDistinct(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: int
        """
        if not t: 
            return 1
        if not s:
            return 0
        res = [0 for i in xrange(len(t)+1)]
        res[0] = 1  
        
        for i in xrange(1,len(s)+1):
            pre = res[0]
            for j in xrange(1,len(t)+1):
                tmp = res[j]        
                if s[i-1] == t[j-1]:
                    res[j] += pre
                pre = tmp
        return res[-1]

 

posted on 2016-05-25 20:31  Sheryl Wang  阅读(160)  评论(0编辑  收藏  举报

导航