我得到了一个大长度的字符串(例如100,000)和一个整数k,我必须计算最大子字符串的长度,该子字符串在给定的字符串中至少重复k次。我找到了对这个特定问题here和here的答案,但是我想知道除了后缀树之外,是否还有其他有效的方法可以解决这个问题?
评论中进行了大量讨论,我认为最好写一个答案进行总结。 TL; DR Longest substring repeating atleast k times
[效率较低,但是比后缀树更容易理解:您需要知道的只是多项式哈希和二进制搜索。
在此阅读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]
部分,因此我们必须摆脱~~~
部分中的部分。~~~
中的h[r]
部分比p^(r-l)
中的大h[l]
倍,这解释了line a
b行
在使用% mod
进行操作时有点神奇,a行之后的value
可以为负,因此value%mod + mod
肯定为正。同时,如果line a value
大于value%mod + mod
后mod
为正,那么(value%mod + mod) % mod
肯定会返回范围0,1,...,mod-1 。最后,mod
是一个大质数,例如10 ^ 9 + 7
value
是一个小数,但大于任何可能的ASCII代码,例如239所以)。一些注意事项:
mod
个散列可能的值,但是字符串的数目是无限的。在文章中阅读如何处理它。h[r] - h[l]*pow[r-l]
之类的操作可能需要使用64位类型的整数。只需在Wikipedia上阅读它,没有什么具体的https://en.wikipedia.org/wiki/Binary_search_algorithm。
假设我们预先计算了数组h
和pow
。让我们进行二进制搜索来查找字符串的最大长度ans
,以使给定字符串k
中存在s
个或更多这样的子字符串。
为什么二进制搜索在这里起作用?因为如果存在某个长度x
,例如k
的长度为s
,则有x
个或更多个相等的子字符串,那么在k
的长度为s
内,肯定有x-1
个或更多个相等的子字符串(只是从我们的比赛中删除最后一个字母)。
二进制搜索将如何工作?假设我们当前正在测试是否存在k
个或更多个相等的长度为mid
的子字符串。我们将计算所有长度为mid
的哈希(使用get_substring_hash
),如果没有k
个相等的哈希,我们将决定更改二进制搜索的边界。
例如:s =“ abcabcdefgdefgdefgdefg”,k = 3
。答案是“ defgdefg”:二进制搜索迭代,如果使用类似[ C0]检查是否存在> = k个哈希。我真的希望现在一切都清楚了。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)