给定一个字符串,我想找到可以由原始字符串形成的所有子字符串,这些子字符串可以被整数k整除。例如,字符串14917可以形成7个可被整数7整除的子字符串。这些子字符串为:14、1491、14917、49、91、917和7。我想出了一个解决方案,但它没有输入大字符串时可以高效运行。我的代码是
string = '14917'
divider = 7
count = 0
for i in range(len(string)):
for j in range(i+1, len(string)+1):
sub_string = string[i:j]
if int(sub_string) % divider == 0:
count += 1
print(count)
我已经阅读了解决此类问题的快速方法,其中大多数讨论了计算字符串的滚动余数,但我无法真正正确地实现它。有什么方法可以快速解决此问题。预先感谢。
答案 0 :(得分:3)
这里概述了如果我们只是想要计数时如何解决此问题,我们不介意有多种方法可以拉出相同的子字符串,并且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
次,然后表示i*(i-1)/2
(可能是相同的)可被7整除的子字符串。
如果要获取唯一的子字符串,则必须做很多的工作。但是,如果我们生成后缀树,以便可以相对快速地计算重复项,那么我们仍然可以O(length of string)
。
要实际产生数字,此方法仍为O(n^2)
。但这将比您现有的大字符串方法更快,因为您只使用小整数进行数学运算。从字符串到成千上万的数字的转换不是特别快...
因此,这里有更多有关后缀树方法计算唯一子字符串的复杂性的详细信息。正确起来要困难得多。
以上,我们从字符串的末尾开始回到开头,并跟踪最后的余数。但是,这意味着特定数字加到其余数字的方式取决于其在字符串中的位置。但是,在一棵树中,给定节点与字符串末端的高度不同。这使得特定节点上的余数难以计算。
我们需要做的是计算某种余数,其中,当前数字的贡献取决于其高度,以保持当前数字的贡献固定。诀窍是将冒泡的可能集乘以10-1
。当且仅当以k
除以此处开头的数字时,我们将获得0。 10-1 (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
。因此,在最终答案中我们不需要这样做,但是在中间计算中我们确实需要这样做!