一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情。
\
最终需要被解决的问题是:字符串s 中可以选择出多少 子串t。
凭直觉应该猜测可能可以通过 DP 解决。但是怎么做状态的转移呢?
为此,考虑一个简单情况的简单的 弱化的问题。
- 假设给定一个三个字符的字符串,问能够选出多少个子串。
似乎还是不太清晰,因此更进一步简化,
- 一个二进制字符串,问存在多少的 010 子串。
这个问题可以尝试用 dp 求解。
- 考虑以 i 结尾处,有多少长度匹配的子串,0,1,2为长度。
对于第一个字符 0 : 它的计算就是求相应位置之前有多少的 0 。
假设我们用用一个 [0, 0, 0]\ 列表计数。
那么有,依赖以前的值以及当前值是否为0
注意这里是 ,所以显然 时要单独计算。而在 下标处仅仅需要考虑它是否为 0 字符。
注意到第二位 “1” 和第三位 “0” 都是需要依赖其以前的计数值,所以可以考虑从下标 1 开始dp,且该二者初值为 0.
对于第二位的值 1 ,不论当前位置能不能凑出到第二位的子串,它之前的 “01” 串是已有的,可以保留。 也就是说至少有这个表达式:
如果当前位置正好匹配(当前值为“1”),那么它还可以组成前面 0 字符的个数 的 “01” 串。 也即此时有
接下来考虑第三位,可以发现规律与第二位是一致的。
可能有疑惑的点在于
这里注意,实际上应该从 dp[2][2] 才有实际的可能计算出结果。 但是,由于前面都是0,所以不可能有值。 类似的,在 下标1处 依赖的是 dp[0][2] + dp[0][1] * ?
它们即使条件成立(当前值为“0")也会得到 0 值。
由此,合理推测,如果有第 4 个值,也是一致的。
进一步的,如果使用的不是二进制串也是成立的。
更进一步地,我们把长度为3的列表换成和待处理的同样长的列表即可。
于是可以得到如下的代码:
def dp(s, target):
dp = [[0] * len(target) for _ in range(len(s))]
dp[0][0] = int(s[0] == target[0])
for i in range(1, len(s)):
dp[i][0] = dp[i - 1][0] + int(s[i] == target[0])
for j in range(1, len(target)):
if s[i] == target[j]:
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1]
else:
dp[i][j] = dp[i - 1][j]
return dp[-1][-1]