在cmets里面讨论的比较多,我觉得还是写个答案总结一下比较好。 TL;博士Longest substring repeating atleast k times
还有一种效率较低的方法,但它确实比后缀树更容易理解:您只需要知道多项式哈希和二分查找即可。
1。字符串多项式哈希
在此处阅读 https://cp-algorithms.com/string/string-hashing.html。以下是该技术的简短说明。
假设我们有字符串s、整数p 和mod。现在我们可以定义哈希函数了:
hash(s) = (ord(s[0])*p^(len(s)-1) + ord(s[1])*p^(len(s)-2) + ... + ord(s[len(s)-1])*p^0) % mod
其中ord 是一个按字符返回整数的函数(假设它是一个字符的ASCII 码)。 O(len(s))中字符串的每个前缀可以很容易地计算多项式哈希:
# h[i] is a hash of prefix of length i.
# For example s = "abacaba",
# h[0] = hash("") = 0
# h[1] = hash("a")
# h[2] = hash("ab")
# ...
# h[7] = hash("abacaba")
h[0] = 0
for i in 1..n:
h[i] = (h[i-1] * p + ord(s[i-1])) % mod
另外让我们预先计算数组pow中的p^0 % mod, p^1 % mod, ..., p^len(s) % mod:
# pow[i] is (p^i) % mod
pow[0] = 1
for i in 1..n:
pow[i] = (pow[i-1] * p) % mod
使用数组h 和pow,我们可以轻松计算字符串s 的任何子字符串的哈希:
# get_substring_hash returns hash(s[l] + s[l+1] + ... + s[r-1]).
def get_substring_hash(s, l, r):
value = h[r] - h[l]*pow[r-l] # line a
return (value%mod + mod) % mod # line b
让我们了解为什么上面的代码有效。
h[r] = (ord(s[r-1])*p^0 + ord(s[r-2])*p^1 + ... + ord(s[l-1])*p^(r-l) + ord(s[l-2])*p^(r-l+1) + ...) % mod
h[l] = ( ord(s[l-1])*p^0 + ord(s[l-2])*p^1 + ...) % mod
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如您所见,我们只需要来自h[r] 的^^^-part,因此我们必须摆脱~~~-part。 ~~~-part in h[r] p^(r-l) 比 h[l] 大倍,这解释了 line a。
Line b 使用% mod 操作时有点神奇,line a 之后的value 可以是负数,所以value%mod + mod 肯定是正数。同时如果value在line a之后为正,value%mod + mod大于mod,那么(value%mod + mod) % mod肯定会返回0、1、...范围内的值, mod-1.
最后,mod 是一个大素数,如 10^9+7,value 是一个小数,但比任何可能的 ASCII 码如 239 em>(阅读文章为什么会这样)。
一些注意事项:
- 哈希可能会发生冲突,因为我们只有
mod 可能的哈希值,但字符串的数量是无限的。如何处理请参阅文章。
- 像
h[r] - h[l]*pow[r-l] 这样的操作可能需要使用 64 位类型的整数。
2。二分查找
只需在 Wikipedia 上阅读,没有具体的 https://en.wikipedia.org/wiki/Binary_search_algorithm。
3。最长重复至少k次的子串解
假设我们预先计算了数组h 和pow。让我们进行二分搜索以找到字符串的最大长度ans,使得在给定的字符串s 中有k 或更多这样的子字符串。
为什么二进制搜索在这里有效?因为如果有一些长度x,例如在s 中有k 或更多相等的子字符串,长度为x,那么在s 中肯定有k 或更多相等的子字符串,长度为x-1(只是从我们的比赛中删除最后一个字母)。
二分查找如何工作?假设我们目前正在测试是否有k 或更多长度相等的子字符串mid。我们将计算长度为mid(使用get_substring_hash)的所有哈希值,如果k 的哈希值相等,我们将决定更改二分搜索的边界。
例如:s = "abcabcdefgdefgdefgdefg", k = 3。答案是“defgdefg”:
abcabcdefgdefgdefgdefg
^^^^^^^^ occurence 1
^^^^^^^^ occurence 2
^^^^^^^^ occurence 3
二分搜索迭代:
l = 1, r = 22, mid = 11. No substring of length 11 satisfy.
l = 1, r = 10, mid = 5. There should be hash("defgd") be seen 3 times.
l = 5, r = 10, mid = 7. There should be hash("defgdef") be seen 3 times.
l = 7, r = 10, mid = 8. There should be hash("defgdefg") be seen 3 times.
l = 8, r = 10, mid = 9. No substring of length 9 satisfy.
l = 8, r = 8. That means answer is 8.
如您所见,复杂度为 O(n log n):round(log n) 二分查找迭代和 O(n) 每次迭代的复杂度,如果您使用类似 std::unordered_map 的东西来检查是否存在 >= k 出现的散列。
我真的希望现在一切都清楚了。