LC 8020. 字符串转换
https://leetcode.cn/problems/string-transformation/description/
这题最开始我拆解问题思路是没有问题的:找出循环切分点,并且动态规划计算总数。但是两个子问题的求解方式都有点问题。
找出循环切分点我想使用hash算法来减少字符串匹配。但是hash算法只能快速过滤掉不一致的情况,如果hash value相同的话还需要做一次比较。最坏情况下类似 "aaa…aaa" 和 "aaaa..aaa" 这样的话就有大量的字符串匹配。 正确的方法是使用KMP来加速匹配(这个好像也是我遇到过的少有的题目,必须使用KMP算法来匹配字符串的)
动态规划计算这个想的就更偏了。我最开始尝试的算法是,假设起始点是i, 每次挪动距离可以是[1,n], 需要挪动k次,那么最终点落在0这个位置上有多少种方法。但是显然这种方法计算量巨大,因为这个转移状态太大了。
其实这个状态有点想的太细了,太细了才会导致计算量巨大。如果可以把整个状态做更大的抽象(或者是统一的话),那么这个状态就特别小。
- dp[i][0] 表示 i次操作之后s
= t的方案数, dp[i][1] 表示 s !t. - 后面C表示有多少个切分点可以导致一次循环之后s == t.
- dp[i][0] = dp[i-1][0] * (C-1) + dp[i-1][1] * C. 其中C-1是因为上次相同的话,我们必须挪动一次
- dp[i][1] = dp[i-1][0] * (n-C) + d[i-1][1] * (n-1-C). 如果前一次相同的话,我们有C-1中可能造成相同,因为一共有n-1种挪动方式,所以有n-C种变为不同。
- 可以看到这里的状态转移,完全就是考虑之前是相同还是不同,到现在这个状态是相同还是不同。转移粒度更粗,状态空间也更小了。
很早之前看过课本上的kmp算法,感觉有点不太好理解,因为里面构建状态是类似backoff的状态,而不是上次match的状态。我觉得题解里面给出的模板挺好的,可以按照这个方式去理解一下。
def mat_mul(a, b, MOD):
R, K, C = len(a), len(a[0]), len(b[0])
res = [[0] * C for _ in range(R)]
for k in range(K):
for i in range(R):
for j in range(C):
res[i][j] += (a[i][k] * b[k][j]) % MOD
res[i][j] %= MOD
return res
def FindCutByKMP(s, t):
n = len(s)
def compute_max_match(pattern):
match = [0] * len(pattern)
c = 0
for i in range(1, len(pattern)):
v = pattern[i]
while c and pattern[c] != v:
c = match[c - 1]
if pattern[c] == v:
c += 1
match[i] = c
return match
def kmp_search(text, pattern):
match = compute_max_match(pattern)
match_count = c = 0
for i, v in enumerate(text):
v = text[i]
while c and pattern[c] != v:
c = match[c - 1]
if pattern[c] == v:
c += 1
if c == len(pattern):
match_count += 1
c = match[c - 1]
return match_count
cuts = kmp_search(s + s[:-1], t)
return cuts
def ComputeMM(c, k, s, t):
# f[i][0] after i operations, s == t
# f[i][1] after i operations, s!= t
# f[i][0] = f[i-1][0] * (c-1) + f[i-1][1] * c
# f[i][1] = f[i-1][0] * (n-c) * f[i-1][1] * (n-1-c)
# f[0][0] = 1 if s == t
MOD = 10 ** 9 + 7
n = len(s)
base = [[c - 1, c], [n - c, n - 1 - c]]
eq = 1 if (s == t) else 0
T = [[eq], [1 - eq]]
while k:
if k & 0x1:
T = mat_mul(base, T, MOD)
base = mat_mul(base, base, MOD)
k = k >> 1
return T[0][0]
class Solution:
def numberOfWays(self, s: str, t: str, k: int) -> int:
cuts = FindCutByKMP(s, t)
if cuts == 0: return 0
return ComputeMM(cuts, k, s, t)