多少子串组合

86 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

\

最终需要被解决的问题是:字符串s 中可以选择出多少 子串t

凭直觉应该猜测可能可以通过 DP 解决。但是怎么做状态的转移呢?

为此,考虑一个简单情况的简单的 弱化的问题

  • 假设给定一个三个字符的字符串,问能够选出多少个子串。

似乎还是不太清晰,因此更进一步简化

  • 一个二进制字符串,问存在多少的 010 子串。

这个问题可以尝试用 dp 求解。

  • 考虑以 i 结尾处,有多少长度匹配的子串,0,1,2为长度。

对于第一个字符 0 : 它的计算就是求相应位置之前有多少的 0 。

假设我们用用一个 [0, 0, 0]\ 列表计数。

那么有,依赖以前的值以及当前值是否为0

dp[i][0]=dp[i1][0]+s[i]==0dp[i][0] = dp[i-1][0] + s[i]==0

注意这里是 i1i-1 ,所以显然 i=0i = 0 时要单独计算。而在 00 下标处仅仅需要考虑它是否为 0 字符。

注意到第二位 “1” 和第三位 “0” 都是需要依赖其以前的计数值,所以可以考虑从下标 1 开始dp,且该二者初值为 0.

对于第二位的值 1 ,不论当前位置能不能凑出到第二位的子串,它之前的 “01” 串是已有的,可以保留。 也就是说至少有这个表达式:

dp[i][1]=dp[i1][1]dp[i][1] = dp[i-1][1]

如果当前位置正好匹配(当前值为“1”),那么它还可以组成前面 0 字符的个数 的 “01” 串。 也即此时有

dp[i][1]=d[i1][1]+d[i1][0]dp[i][1] = d[i-1][1] + d[i-1][0]

接下来考虑第三位,可以发现规律与第二位是一致的。

可能有疑惑的点在于

dp[1][2]=?dp[1][2] = ?

这里注意,实际上应该从 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]