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

Python 查找可被数字k整除的字符串的子字符串数,python,string,python-3.x,algorithm,math,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(

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

我读过关于解决这类问题的快速方法,其中大部分都谈到了计算字符串的滚动余数,但我无法真正正确地实现它。有没有什么办法可以迅速解决这个问题。提前感谢。

这里是如何解决这个问题的一个概要,如果我们只想计算,我们不介意有多种方法可以将同一子串拉出,并且
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(字符串长度)

要实际生成数字,这种方法仍然是
O(n^2)
。但它将比现有的处理大字符串的方法更快,因为您只需要用小整数进行数学运算。将字符串转换为数千位数的数字并不是特别快


因此,这里更详细地介绍了后缀树方法计算唯一子字符串的复杂性。要做到正确要困难得多

在上面,我们从字符串的末尾回到开头,跟踪最后的剩余部分。但这意味着,一个特定的数字在余数中添加的内容取决于它在字符串中的位置。但是,在树中,给定节点与字符串末端的高度不同。这使得特定节点上的余数更难计算

我们需要做的是计算某种余数,其中当前数字的贡献取决于其高度,从而保持当前数字的贡献不变。其诀窍是将冒泡的可能余数集乘以
10-1
。然后我们将得到0,当且仅当从这里开始的数字可被
k
整除。
10-1(mod k)
是什么意思?它表示一个数字
m
,使得
(10*m)%k
1
。通过检查可以看出,
5
适用于
7
,因为
50=7*7+1
。通过反复试验,我们总能找到相反的结果。一般来说,可以通过以下方式更有效地确定其存在和价值。无论哪种方式,在我们的例子中都是
5

现在,将余数集乘以一个数字(而不是当前数字)需要更多的工作,但这样做的好处是,我们可以合并树的分支。例如,考虑<代码> 5271756 的后缀树。(请注意,唯一性很重要,因为字符串
7
出现两次。)

现在我们可以回到树上,找到剩余物的计数。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}
在这一点上,我们有两个可以被0整除的字符串,即
7
756

从根开始填充整棵树,然后以同样的方式返回(手工操作,我可能会犯错误,而且在第一次时就犯了很多错误!):

由此我们得出结论,有
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
。所以我们在最终答案中不需要这个,但我们在中间计算中确实需要

我怀疑你在概念上会比这更有效率。例如,对于除数7,十进制没有任何可以用于增量计算的好结构。我看到的唯一可以改进的是数字解析的东西。你想按顺序显示子串,或者你想考虑字符串的所有组合吗?我同意尼可。从循环体中删除字符串操作将加快代码的速度(但不会改变时间复杂度)。换句话说,将字符串转换为循环前的数字列表。k可以有多大?@Trenton_M取决于问题的规格(未给出),这可能是一个缺陷,也可能是期望的行为。感谢您的解释!我只是想问一下后缀树。因为在我的例子中,我需要找到所有唯一的子字符串,所以我可能需要检查重复的子字符串。构建后缀树的最佳策略是什么?@SiddhantDube称之为标准方法。但计算将变得棘手。我会考虑细节并加上一条注释。@SiddhantDube这比我想象的要难,但我解释了原则。在编写通用代码之前,您肯定会想编写一些玩具示例,因为它有点复杂。
(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
175 (af)
5271 (cba)
52717 (cbaf)
5271756 (cbafe)
56 (ce)
7 (d)
7175 (daf)
756 (dcf)