重复字符串的判定问题(质数检验法)

38 篇文章 0 订阅
最近遇到一个问题,需要判定一个字符串是否是重复的. 初一看这个问题比较简单,但实际上并不如此。复杂就复杂在不知道重复单元的长度。
举例说明重复字符串的定义
abcabcabc 以 abc 为重复单元重复 3 次
abcdabc    不重复
abababababab 以 ab 为单元重复 6 次,或以 abab 为单元重复 3 次,或者以 ababab 为单元重复 2 次
不需要想的方法是切字符串,一个一个试,但这个方法时间复杂度有点高,估计可以优化的地方是充分利用上一次判断的信息来指导当次判断 --- 这也是很多算法优化的思路。
无法忍受这个方法。但有没有更高效的解决办法呢?
刚好这段时间闲来无事,翻过几页《初等数论及其应用》这本书,看到了有关质数的话题,于是比较敏感的就想到了,如果一个字符串是重复的,那么它必然可以分解为两个数字,设 len = L*N, len 为字符串长度,L 为重复串的重复单元,N 为重复次数.
更进一步地,如果 L 本身是重复的,则可以拆解 L ,使 L 更短,N 更大,这又是一种重复局面。实际上,只需要 L 满足存在前后缀相等则 L 就可拆解。(这一算法在下一篇文章中介绍)
下面给出一个判断长 len 的字符串是否是重复的判定算法,并求 L 和 N。

算法思路:通过质数方法检验一个长度为 len 的字符串 str 是否是重复的
如果 str 是重复的,设最小重复单元长度为 L ,重复 N 次
len = L*N
str[0, len) 可以写为 str[0,L), str[L,2*L), str[2*L,3*L).... str[(N-1)*L, N*L)
由式子,若 L*N 是一个正确的重复表达,则可以扩张 L ,收缩 N 寻找到更大重复单元的表示。
len = L*N = (L*N1)*N2 = (l*N1*N3)*N4 = ...
也即是当 N 为合数时,可以将它的因子乘到 L 上,使 L 扩张.
当 N 收缩到最小时必然是一个质数(或1, 此时每个字符都相等),此时我们得到了一个最大重复单元的表示: 也就是说, 最大重复单元的表示必然存在,此时 N 是质数
因此,我们寻找这个表示的算法可以根据输入的 len,从小到大扫描质数表 pi,假定为 P 值,
len/P 为 L,然后判断一下输入 str 是不是以 L 为单元重复 N 次. 如果是则找到了.这个方法把寻找重复表示的问题转化为了判断重复表示。
上面已经指出,由 len = L*P 可以得到一族重复表示: 将闭开区间字符串 [0,L) 单独拿出,迭代此算法,如果它仍然
是一个重复的,设为 L=LL*LP,则 len = LL*(LP*P), 对 [0,LL) 迭代,若还能展开,则继续寻找
直到无法展开,这样下去,可以得到一族表示。这一族可以称为以 P 为基的表示.


注意,当 L 是前后缀相等的,则此时 L 要减去前后缀相等的长度又是一个新的重复单元,它是以另外一个质数为基的表示,并不是以 P 为基。

下面从另一个角度来证明,以另一个数为基的重复表示,不能断言不存在。
证明:若 len=L1*P1 是一个重复表示,不能断言必然不存在其它的表示: len = L2*P2, P2 为质数且 P1≠P2 

------------------------------------------------------------------------------------------------
先证明如下引理:
若 p1, p2 是重复周期,即 len = p1*x = p2*y,必存在 p1=n*p2+k (这个式子就相当于求出 n 和 k : n=p1/p2, k=p1%p2), k<p1, k<p2,则 k 也是重复周期.
证明: 
由 p1=n*p2+k 及 p1, p2 均为周期,可知
p1 = n*p2 + k
p1 + 1 = n*p2 + k+1
p1 + 2 = n*p2 + k+2
...
...
所以有:
[p1] = [n*p2 + k]
[p1 + 1] = [n*p2 + k+1]
[p1 + 2] = [n*p2 + k+2]
...
进一步地
[p1] = [0] = [n*p2 + k]   = [k]
[p1 + 1] = [1] = [n*p2 + k+1] = [k+1]
[p1 + 2] = [2] = [n*p2 + k+2] = [k+2]
...
那么
[0] = [k]
[1] = [k+1]
[2] = [k+2]
...
[q] = [k+q]
这说明,k 也是一个周期. 
于是就证明了,若 p1=n*p2+k 且 p1 和 p2 是一个周期,则 k 也是周期.


------------------------------------------------------------------------------------------------
假设 P1 < P2 且都是质数, len=L1*P1 = L2*P2, 都是重复表示.L1>L2, 必然存在一个整数C使得: L1=C*P2, L2=C*P1, len=C*P1*P2.
由上面的引理,L1, L2 都是周期,则 L1%L2 也是周期,也即
T1=(C*P2)%(C*P1) = C*(P2%P1)是周期.
迭代:
C*(L2%T1) = C*(C*P1%C*(P2%P1)) = C*(P1%(P2%P1)) 是周期


C*((P2%P1)%(P1%(P2%P1)))
...
该结果会收敛于 C.
这是假设存在另一种表示 L2*P2 得到的结论,它并不与假设矛盾或者与已知的任何结论冲突.
当 C=1 时,周期为1也就说明了字符串每个字符都一样,此时 L2*P2 必然存在


举例而言: len = 6*2 = 4*3, P1=2, P2=3, L1=6, L2=4;
假设 L1*P1 是一个重复表示则 x0=x6, x1=x7, x2=x8, x3=x9, x4=x10, x5=x11
假设 L2*P2 是一个重复表示则 x0=x4=x8, x1=x5=x9, x2=x6=x10, x3=x7=x11
若上面两个假设同时成立则:
x0=x2=x4=x6=x8=10
x1=x3=x5=x7=x9=x11
此时周期很明显为 2, 这也符合上面的证明:
若 L1=6, L2=4 是周期, 由 P1=2, P2=3 求得 C=12/(P1*P2)=2, 
则 C*(P2%P1)=2 是周期.


实例 Str = abababababab
则确实可以将 Str 看待为长为6的串重复两次,也可以认为长为 4 的串重复三次.


所以也就证明了 “无法断言L2*P2 必定不存在”这个命题。


下面给出求 L2*P2 的两种方法.
1.  L2 < L1 实际上已经暗含了求 L 的方式: L1 包含 L2, 充分必要条件是 L1 前后缀
匹配, 到这儿解决办法其实已经很明显了: 与 kmp 算法求重复单元一样.

2.  继续在质数表中将质数赋值给 N,往后面寻找,


由于目前质数表已经非常巨大了,我们实际解决的问题不会有那么大的规模,因此该算法是适用的。

需要注意的是,由著名的素数定理,设 π(x) 为小于 x 的质数的个数,则  π(x) 与 x/logx 之比趋于 1,也即小于 x 的质数个数是 x/logx

上面算法最坏的可能是,对于每一个小于 x 的质数,都要去作重复判定 。

可以估算一下这个值大小,由于 √x > logx,所以 x/√x < x/logx,即  √x < x/logx. 即这个数比 √x 要大

综上,我所最多需要判断大概  x/logx ,而每次判断的时间复杂度是线性的,故该算法对 x 很大时,时间复杂度是

O(x) = x*x/logx.


代码见:https://github.com/juniorfans/repeatString

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值