查找可以被数字k整除的字符串子字符串的数量

时间:2018-10-01 16:56:42

标签: python string python-3.x algorithm math

给定一个字符串,我想找到可以由原始字符串形成的所有子字符串,这些子字符串可以被整数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) 

我已经阅读了解决此类问题的快速方法,其中大多数讨论了计算字符串的滚动余数,但我无法真正正确地实现它。有什么方法可以快速解决此问题。预先感谢。

1 个答案:

答案 0 :(得分:3)

这里概述了如果我们只是想要计数时如何解决此问题,我们不介意有多种方法可以拉出相同的子字符串,并且k相对于107是)而言。

首先,让我们从数字的最后一位到第一位,跟踪整个数字的其余部分。对于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) % k1。通过检查可以看出,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整除,即7756

从树根开始填充整个树,然后以相同的方式冒泡(手工完成,我可能会犯错误-并在第一次出现很多错误!):

(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的方式是什么意思?这意味着存在3s子字符串( (s%7) * (5^(len(s)-1)) ) %7 == 2。因此,在最终答案中我们不需要这样做,但是在中间计算中我们确实需要这样做!