题目链接
Leetcode.1316 不同的循环子字符串
rating : 1837
题目描述
给你一个字符串 text
,请你返回满足下述条件的 不同 非空子字符串的数目:
- 可以写成某个字符串与其自身相连接的形式(即,可以写为
a + a
,其中a
是某个字符串)。
例如,abcabc
就是 abc
和它自身连接形成的。
示例 1:
输入:text = “abcabcabc”
输出:3
解释:3 个子字符串分别为 “abcabc”,“bcabca” 和 “cabcab” 。
示例 2:
输入:text = “leetcodeleetcode”
输出:2
解释:2 个子字符串为 “ee” 和 “leetcodeleetcode” 。
提示:
- 1 ≤ t e x t . l e n g t h ≤ 2000 1 \leq text.length \leq 2000 1≤text.length≤2000
text
只包含小写英文字母。
解法 : 字符串哈希
我们从 s
的每一个位置开始暴力枚举,时间复杂度是
O
(
n
3
)
O(n^3)
O(n3) 的,会超时。
会超时的代码:
class Solution {
public:
int distinctEchoSubstrings(string s) {
int n = s.size() , ans = 0;
unordered_set<string> uset;
for(int i = 0;i < n;i++){
int m = (n - i) / 2;
//从位置 i 开始 , 枚举长度为 len 的子串 t , 以及跟在 t 后面长度同样为 len 的子串 p
//如果 t == q 并且 t 是一个新的子串那么我们就记录答案
for(int len = 1;len <= m;len++){
auto t = s.substr(i , len) , p = s.substr(i + len,len);
if(uset.count(t)) continue;
if(t == p){
ans++;
uset.insert(t);
}
}
}
return ans;
}
};
对于上面的超时的代码,我们可以通过 字符串哈希 来优化这个枚举子串的过程。
字符串哈希:字面意思就是一个数来唯一的表示一个字符串,这样的话我们比较两个字符串是否相等,就可以通过它们各自的哈希值来 O ( 1 ) O(1) O(1) 的比较。
关于字符串哈希可以看这篇博客:字符串哈希。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
C++代码:
using ULL = unsigned long long;
const int P = 13131;
class Solution {
public:
int distinctEchoSubstrings(string s) {
int n = s.size() , ans = 0;
unordered_set<ULL> uset;
//使用无符号数来计算哈希值,就不需要取模操作了
ULL p[n + 1],h[n + 1];
memset(p,0,sizeof p);
memset(h,0,sizeof h);
p[0] = 1;
//计算 s 哈希值的前缀和
for(int i = 1;i <= n;i++){
h[i] = h[i - 1] * P + s[i - 1];
p[i] = p[i - 1] * P;
}
//返回区间 [l ,r] 的哈希值
auto get = [&](int l,int r){
return h[r] - h[l - 1] * p[r - l + 1];
};
for(int i = 0;i < n;i++){
int m = (n - i) / 2;
for(int len = 1;len <= m;len++){
//因为 h 下标是从 1 开始的 , 所以所有下标都要 + 1
//[i , i + len - 1] -> [i + 1 , i + len]
//[i + len , i + 2*len - 1] -> [i + len + 1 , i + 2*len]
auto l = get(i + 1,i + len) , r = get(i + len + 1,i + 2 * len);
if(uset.count(l)) continue;
if(l == r){
uset.insert(l);
ans++;
}
}
}
return ans;
}
};