如果我们只是想要计数,这里是如何解决这个问题的大纲,我们不介意有多种方法可以提取相同的子字符串,而k相对素数到 10(7 是)。
首先让我们从数字的最后一位数到第一位,跟踪整数的余数。对于14917,这意味着编译下表:
number 10**digits % 7 digit remainder
0
7 1 7 0+1*7 -> 0
17 3 1 0+3*1 -> 3
917 2 9 3+2*9 -> 0
4917 6 4 0+6*4 -> 3
14917 4 1 3+4*1 -> 0
现在这是诀窍。每当你在两个地方看到相同的余数时,从一个到另一个,你得到的东西可以被 7 整除。例如,在两个 3 之间你得到 49。如果一个特定的值出现 i 次,那么表示可被 7 整除的 i*(i-1)/2(可能相同)子字符串。
如果我们想获得唯一的子字符串,那么我们必须做更多的工作。很多。但是如果我们生成一个后缀树,我们仍然可以是O(length of string),这样我们就可以相对快速地计算重复项。
要实际产生数字,这种方法仍然是O(n^2)。但它会比你现有的大字符串方法更快,因为你只用小整数做数学。将字符串转换为数千位数的数字并不是特别快...
所以这里有更多关于唯一子串计数的后缀树方法的复杂性的详细信息。做对要困难得多。
上面我们从字符串的结尾回到开头,跟踪最后的剩余部分。但这意味着特定数字添加到余数中的内容取决于它在字符串中的位置。然而,在树中,给定节点与字符串末端的高度不同。这使得特定节点的余数更难计算。
我们需要做的是计算某种余数,其中当前数字的贡献取决于其高度,而不是保持当前数字的贡献固定。诀窍是将冒泡的可能余数集乘以10<sup>-1</sup>。然后当且仅当从这里开始的数字可以被k 整除时,我们将得到 0。 10<sup>-1</sup> (mod k) 是什么意思?它表示一个数字m,这样(10*m) % k 就是1。通过检查可以看出5 适用于7,因为50 = 7*7 + 1。我们总是可以通过反复试验找到相反的结果。一般来说,通过Euler's Theorem可以更有效地确定它的存在和价值。无论哪种方式,在我们的例子中都是5。
现在,将余数集乘以一个数字而不是当前数字是更多的工作,但这样做的好处是我们可以合并树的分支。例如,考虑5271756 的后缀树。 (请注意,唯一性很重要,因为字符串 7 出现了两次。)
(root):
a
b
c
d
e
(a): '17'
f
(b): '27'
a
(c): '5'
b
e
(d): '7'
a
f
(e): '6'(end)
(f): '5'
e
现在我们可以按照自己的方式备份树来查找余数。 756 的计算说明了这个想法:
digit prev_remainders remainders
# for 6
6 {} {(6)%7: 1}
# for 5 56
5 {6: 1} {(5)%7: 1, (5+5*6)%7: 1}
{ 5: 1, 0: 1} = {0:1, 5:1}
# for 7 756 75
7 {0: 1, 2:1} {(7)%7: 1, (7+5*0)%7: 1, (7+5*5): 1}
{ 0: 1, 0: 1, 4: 1} = {0:2, 4:1}
因此,从那里开始,我们有 2 个可被 0 整除的字符串,即 7 和 756。
从根开始填充整棵树,然后以同样的方式冒泡(手工完成,我可能会犯错误 - 第一次就犯了很多错误!):
(root): {0:8, 1:6, 2:3, 4:1, 5:4, 6:4}
a
b
c
d
e
(a): '17' {0:1, 1:3}
f
(b): '27' {2:3, 6:3}
a
(c): '5' {0:4, 1:3, 5:1}
b
e
(d): '7' {0:3, 4:1, 5:3}
a
f
(e): '6'(end) {6:1}
(f): '5' {0:1, 5:1}
e
从中我们得出结论,有8 子串可以被7 整除。事实上它们是:
175 (af)
5271 (cba)
52717 (cbaf)
5271756 (cbafe)
56 (ce)
7 (d)
7175 (daf)
756 (dcf)
剩下的呢?例如,有3 获取2 的方法是什么意思?这意味着有3 子字符串s 这样( (s%7) * (5^(len(s)-1)) ) %7 == 2。所以我们在最终答案中不需要它,但我们在中间计算中肯定需要它!