题目
Given a string S and a string T, count the number of distinct subsequences of S which equals T.
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).
Example 1:
Input: S = "rabbbit", T = "rabbit"
Output: 3
Explanation:
As shown below, there are 3 ways you can generate "rabbit" from S.
(The caret symbol ^ means the chosen letters)
rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^
Example 2:
Input: S = "babgbag", T = "bag"
Output: 5
Explanation:
As shown below, there are 5 ways you can generate "bag" from S.
(The caret symbol ^ means the chosen letters)
babgbag
^^ ^
babgbag
^^ ^
babgbag
^ ^^
babgbag
^ ^^
babgbag
^^^
思路与解法
这道题题意是让我们从字符串S中删除一些字符,从而使得剩下的字符在不改变相对位置的前提下构成字符串T。我们需要求出的所有满足上述条件的方法数。
起初,可能并没有什么想法,因为字符串S中的字符位置不固定,并且相同的字符可能出现多次,使用一般的暴力搜索解决不了该问题。所以我们应该转变一下思想:考虑我们现在需要将字符串S[:i]
(不包含第i
个字符,下同)经过删除一些字符之后转化为字符串T[:j]
,那么
- 假如
S[i-1]==T[j-1]
,则此时的方法数为①将字符串S[:i-1]
经过删除一些字符之后转化为字符串T[:j]
;②将字符串S[:i-1]
经过删除一些字符之后转化字符串T[:j-1]
。前者表示将S[i]
去除,后者表示不去除S[i]
。 - 假如
S[i-1]!=T[j-1]
,则此时的方法数为:将字符串S[:i-1]
经过删除一些字符之后转化为字符串T[:j]
。
到此处,我们可以使用动态规划来解决此问题,定义数据如下:dp[i][j]
表示将S[:i]
经过删除一些字符之后转发为T[:j]
的方法数。
状态转移方程如下:
// s[i-1]表示的是S的第i个字符
if s[i-1] == t[j-1] {
dp[i][j] = dp[i-1][j] + dp[i-1][j-1]
} else {
dp[i][j] = dp[i-1][j]
}
代码实现(Go)
func numDistinct(s string, t string) int {
lenS := len(s)
lenT := len(t)
dp := make([][]int, 0)
// 初始化dp数组,dp[i][0]表示将S[:i]转化为空串的方法数:1
for i:=0; i<=lenS; i++ {
dp = append(dp, make([]int, lenT+1))
dp[i][0]=1
}
// dp[0][j]表示将空串转化为T[:j]的方法数
for j:=1; j<=lenT; j++ {
dp[0][j]=0
}
// 状态转移
for i:=1; i<=lenS; i++ {
for j:=1; j<=lenT; j++ {
if s[i-1] == t[j-1] {
dp[i][j] = dp[i-1][j] + dp[i-1][j-1]
} else {
dp[i][j] = dp[i-1][j]
}
}
}
return dp[lenS][lenT]
}
测试结果
算法改进
上述算法的时间复杂度为O(N*M)
,控件复杂度也为O(N*M)
。我们可以改进空间复杂度:
由于我们的状态转移方程中dp[i]
都是与dp[i-1]
相关,所以我们可以省掉维持i
的一维空间。但是我们需要做一些修改:内层循环需要反序!!
原因如下:因为dp[i][j]
需要dp[i-1][j]
的值,即我们的第i
次循环需要i-1
循环所记录的值。如果我们的内层循环依然正序,则在第i
次循环时,dp[i-1][j]
的值便被覆盖为dp[i][j]
,使得当我们计算dp[i][j+1]
需要用到dp[i-1][j]
时出现错误。
func numDistinct(s string, t string) int {
lenS := len(s)
lenT := len(t)
dp := make([]int, lenT+ 1)
dp[0] = 1
for i := 1; i <= lenS ; i++ {
for j := lenT ; j >= 1; j-- {
if s[i - 1] == t[j - 1] {
dp[j] += dp[j-1]
}
}
}
return dp[lenT]
}