题目地址:
https://leetcode.com/problems/repeated-substring-pattern/
给定一个长 n n n字符串 s s s,问是否是某个字符串自我重复若干次得到的(这里若干次指的是多于 1 1 1次)。
思路是用KMP算法。先求 s s s的next数组 n e ne ne,但这里数组需要多求一位,目的是求出 s s s的最长相等前后缀的长度,设其为 l l l。如果 l = 0 l=0 l=0,那么显然返回false;我们可以证明,当 l > 0 l>0 l>0时,返回true当且仅当 ( n − l ) ∣ n (n-l)|n (n−l)∣n。
充分性:
若
(
n
−
l
)
∣
n
(n-l)|n
(n−l)∣n,则
n
n
−
l
≥
2
\frac{n}{n-l}\ge 2
n−ln≥2,所以
n
≤
2
l
n\le 2l
n≤2l,设
x
=
n
−
l
≤
l
x=n-l\le l
x=n−l≤l,所以
x
∣
n
x|n
x∣n,由next数组定义知道,
s
[
0
:
x
−
1
]
=
s
[
x
:
2
x
−
1
]
=
s
[
2
x
:
3
x
−
1
]
=
.
.
.
=
s
[
n
−
x
:
n
−
1
]
s[0:x-1]=s[x:2x-1]=s[2x:3x-1]=...=s[n-x:n-1]
s[0:x−1]=s[x:2x−1]=s[2x:3x−1]=...=s[n−x:n−1],也就是说
s
s
s是
s
[
0
:
x
−
1
]
s[0:x-1]
s[0:x−1]自我重复若干次得到的。
必要性:
若
s
=
A
A
.
.
.
A
s=AA...A
s=AA...A,其中
A
A
A代表某个字符串,也就是
s
s
s可以表达为某个字符串自我重复
k
k
k次。当
s
s
s给定时,我们取最短的那个
A
A
A,所以此时
k
k
k也达到最大,有
n
=
k
l
A
n=kl_A
n=klA,则
n
e
[
n
]
≥
(
k
−
1
)
l
A
ne[n]\ge (k-1)l_A
ne[n]≥(k−1)lA。下面我们证明
n
e
[
n
]
≤
(
k
−
1
)
l
A
ne[n]\le (k-1)l_A
ne[n]≤(k−1)lA。反证法,如果
n
e
[
n
]
≥
(
k
−
1
)
l
A
+
1
ne[n]\ge (k-1)l_A+1
ne[n]≥(k−1)lA+1,设
n
e
[
n
]
=
l
ne[n]=l
ne[n]=l,
x
=
n
−
l
x=n-l
x=n−l,由必要性证明知道,
s
[
0
:
x
−
1
]
=
s
[
x
:
2
x
−
1
]
=
.
.
.
=
s
[
u
x
:
(
u
+
1
)
x
−
1
]
s[0:x-1]=s[x:2x-1]=...=s[ux:(u+1)x-1]
s[0:x−1]=s[x:2x−1]=...=s[ux:(u+1)x−1],其中
n
−
1
−
(
(
u
+
1
)
x
−
1
)
<
x
n-1-((u+1)x-1)<x
n−1−((u+1)x−1)<x(这个不等式一定是成立的,否则的话
s
s
s就可以由
s
[
0
:
x
−
1
]
s[0:x-1]
s[0:x−1]重复若干次得到,这与
A
A
A的最短性矛盾),所以有
s
[
n
−
x
:
(
u
+
1
)
x
−
1
]
=
s
[
n
−
1
−
(
(
u
+
1
)
x
−
1
)
:
x
−
1
]
=
s
[
n
−
(
u
+
1
)
x
:
x
−
1
]
s[n-x:(u+1)x-1]=s[n-1-((u+1)x-1):x-1]\\=s[n-(u+1)x:x-1]
s[n−x:(u+1)x−1]=s[n−1−((u+1)x−1):x−1]=s[n−(u+1)x:x−1]这与
n
e
[
n
]
=
l
ne[n]=l
ne[n]=l是矛盾的,因为
n
e
[
n
]
ne[n]
ne[n]还可以拓展,应该有
n
e
[
n
]
>
l
ne[n]>l
ne[n]>l才对。如此一来,
n
e
[
n
]
ne[n]
ne[n]取任何大于
(
k
−
1
)
l
A
(k-1)l_A
(k−1)lA的数都会造成矛盾,只能有
n
e
[
n
]
=
(
k
−
1
)
l
A
ne[n]= (k-1)l_A
ne[n]=(k−1)lA。而
n
−
(
k
−
1
)
l
A
=
l
A
n-(k-1)l_A=l_A
n−(k−1)lA=lA,所以
(
n
−
(
k
−
1
)
l
A
)
∣
n
(n-(k-1)l_A)|n
(n−(k−1)lA)∣n。证毕。
所以我们证明了 s s s是由某字符串自我重复若干次得到的,当且仅当 l > 0 ∧ ( n − l ) ∣ n l>0\land (n-l)|n l>0∧(n−l)∣n。并且顺便知道了,此时 s [ 0 : l − 1 ] s[0:l-1] s[0:l−1]就是最短的可以重复若干次得到 s s s的那个字符串。关于next数组怎么求,参考https://blog.csdn.net/qq_46105170/article/details/106168535。代码如下:
class Solution {
public:
bool repeatedSubstringPattern(string s) {
int n = s.size();
s = ' ' + s;
int ne[n + 1];
ne[1] = 0;
for (int i = 2, j = 0; i <= n; i++) {
while (j && s[i] != s[j + 1]) j = ne[j];
if (s[i] == s[j + 1]) j++;
ne[i] = j;
}
return ne[n] && n % (n - ne[n]) == 0;
}
};
时空复杂度 O ( n ) O(n) O(n)。