前言
新的一年,好好学习
正文
问题来源
本问题来自leetcode上的115题。虽然独立刷出,但感觉并不容易
问题描述
给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数。
一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,”ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)。
示例 1:
输入: S = "rabbbit", T = "rabbit"
输出: 3
解释:
如下图所示, 有 3 种可以从 S 中得到 "rabbit" 的方案。
(上箭头符号 ^ 表示选取的字母)
rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^
示例 2:
输入: S = "babgbag", T = "bag"
输出: 5
解释:
如下图所示, 有 5 种可以从 S 中得到 "bag" 的方案。
(上箭头符号 ^ 表示选取的字母)
babgbag
^^ ^
babgbag
^^ ^
babgbag
^ ^^
babgbag
^ ^^
babgbag
^^^
分析:
因为这题有动态规划这个提示,我们用表的形式填写,尝试写出递推公式。 S = “rabbbit”, T = “rabbit”
r | a | b | g | b | a | a | ||
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
r | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
a | 0 | + | 1 | 0 | 0 | 0 | 0 | 0 |
b | 0 | + | + | 1 | 2 | 3 | 0 | 0 |
b | 0 | + | + | + | 1 | 3 | 0 | 0 |
i | 0 | + | + | + | + | 0 | 3 | 0 |
t | 0 | + | + | + | + | + | 0 | 3 |
子序列S不可能长过序列T,表中+代表不可能,值等于0
S = “babgbag”, T = “bag”
b | a | b | g | b | a | g | ||
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
b | 0 | 1 | 0 | 2 | 0 | 3 | 0 | 0 |
a | 0 | + | 1 | 0 | 0 | 0 | 4 | 0 |
g | 0 | + | + | 0 | 1 | 0 | 0 | 5 |
所以我们可以得到递推公式为:当T[i]==S[j]时(其中i,j为记录dp动态结果二维数组的行列标),上一行最接近j的非0值和本行最接近j的非0值之和。而当i=1(序列第一个)时,T[0]==T[j],依次1,2,3…
本人写的Go代码如下:
func numDistinct(s string, t string) int {
ls := len(s)
lt := len(t)
if ls < lt {
return 0
}
dp := make([][]int, lt)
for i := 0; i < lt; i++ {
dp[i] = make([]int, ls)
}
rawCount := 0
for j := 0; j < ls; j++ {
if t[0] == s[j] {
rawCount++
dp[0][j] = rawCount
}
}
for i := 1; i < lt; i++ {
rawCount = 0
for j := i; j < ls; j++ {
if t[i] == s[j] {
lastCount := 0
lastIndex := i-1
for k := j-1; k >=lastIndex; k-- {
if dp[lastIndex][k] != 0 {
lastCount = dp[lastIndex][k]
break
}
}
rawCount += lastCount
dp[i][j] = rawCount
}
}
}
return rawCount
}
经过研究发现,这并不是最优的。需要将表里的等于0的空间利用,减少时间开销。 S = “rabbbit”, T = “rabbit”
r | a | b | g | b | a | a | ||
---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |
r | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
a | 0 | + | 1 | 1 | 1 | 1 | 1 | 1 |
b | 0 | + | + | 1 | 2 | 3 | 3 | 3 |
b | 0 | + | + | + | 1 | 3 | 3 | 3 |
i | 0 | + | + | + | + | 0 | 3 | 3 |
t | 0 | + | + | + | + | + | 0 | 3 |
子序列S不可能长过序列T,表中+代表不可能,值等于0
S = “babgbag”, T = “bag”
b | a | b | g | b | a | g | ||
---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |
b | 0 | 1 | 1 | 2 | 2 | 3 | 3 | 3 |
a | 0 | + | 1 | 1 | 1 | 1 | 4 | 4 |
g | 0 | + | + | 0 | 1 | 1 | 1 | 5 |
这样省去了寻找上一行最接近j的非0值这个循环。 dp[i][j]表示T的从0开始长度为i的子串和S的从0开始长度为j的子串的匹配的个数。
比如, dp[2][3]表示T中的ra和S中的rab的匹配情况。
(1)显然,至少有dp[i][j] = dp[i][j - 1].
比如, 因为T 中的”ra” 匹配S中的 “ra”, 所以dp[2][2] = 1 。 显然T 中的”ra” 也匹配S中的 “rab”,所以s[2][3] 至少可以等于dp[2][2]。
(2) 如果T[i-1] == S[j-1], 那么dp[i][j] = dp[i][j - 1] + (T[i - 1] == S[j - 1] ? dp[i - 1][j - 1] : 0);
比如, T中的”rab”和S中的”rab”显然匹配,
根据(1), T中的”rab”显然匹配S中的“rabb”,所以dp[3][4] = dp[3][3] = 1,
根据(2), T中的”rab”中的b等于S中的”rab1b2”中的b2, 所以要把T中的”rab”和S中的”rab1”的匹配个数累加到当前的dp[3][4]中。 所以dp[3][4] += dp[2][3] = 2;
(3) 初始情况是
dp[0][0] = 1; // T和S都是空串.
dp[0][1 … S.length() ] = 1; // T是空串,S只有一种子序列匹配。
dp[1 … T.length() ][0] = 0; // S是空串,T不是空串,S没有子序列匹配。
func numDistinct(s string, t string) int {
ls := len(s)
lt := len(t)
if ls < lt {
return 0
}
dp := make([][]int, lt+1)
for i := 0; i <= lt; i++ {
dp[i] = make([]int, ls+1)
}
for j := 0; j <= ls; j++ {
dp[0][j] = 1
}
for i := 1; i <= lt; i++ {
for j := i; j <= ls; j++ {
dp[i][j] = dp[i][j-1]
if t[i-1] == s[j-1] {
dp[i][j] += dp[i-1][j-1]
}
}
}
//fmt.Println(dp)
return dp[lt][ls]
}
总结:
勤思考
结语
不管怎么样好好加油。