难度困难220
给你一个字符串 s
,考虑其所有 重复子串 :即,s
的连续子串,在 s
中出现 2 次或更多次。这些出现之间可能存在重叠。
返回 任意一个 可能具有最长长度的重复子串。如果 s
不含重复子串,那么答案为 ""
。
示例 1:
输入:s = "banana" 输出:"ana"
示例 2:
输入:s = "abcd" 输出:""
提示:
2 <= s.length <= 3 * 104
s
由小写英文字母组成
分析:对于这种寻找最长/最短符合条件的字符序列问题,首先就是需要确定长度,那么实际上很容易会想到二分。我们来看一下二分如何使用:
假设我们现在已知串str是一个满足要求的串,那么很显然str的子串也会满足要求。因此如我们发现在长度为len的情况下存在一个长度为len的str满足重复子串的要求,那么接下来的答案只可能>=len而不会<len,这符合二分的要求。
接下来就是快速判断子串的存在性。假设我们现在已知子串str[i~j],那么下一个同长度的子串str[i+1~j+1]也很容易求出来,只要在str[i~j]的基础上去头加尾即可。
思路一:用哈希表存下子串-超时。
超时的原因在于,哈希表底层在对string求hash值的时候是需要遍历字符串的,因此实际上不能起到很大的优化效果。
class Solution {
public:
// 还要记录起点位置
//二分长度
int check(string& s, int len){
unordered_map<string, int> hash_map;
string hash_str;
int i;
for(i = 0; i < len - 1; ++ i){
hash_str.append(1, s[i]);
}
for(; i < s.size(); ++ i){
hash_str.append(1, s[i]);
if(hash_map.find(hash_str) != hash_map.end()){
return hash_map[hash_str];
}else{
hash_map[hash_str] = i - len + 1;
hash_str = hash_str.substr(1);
}
}
return -1;
}
string longestDupSubstring(string s) {
int l = 1, r = s.size(), m, temp_loc, len_ans = -1, loc = -1;
while(l <= r){
//cout << l << " " << r << endl;
m = (l + r) >> 1;
if((temp_loc = check(s, m)) != -1){
l = m + 1;
len_ans = m;
loc = temp_loc;
}else{
r = m - 1;
}
}
if(loc == -1) return "";
return s.substr(loc, len_ans);
}
};
思路2:将字符串映射为26进制数-部分数据溢出。
这种方法可以避免底层做哈希的时候还去遍历字符串,但有溢出的风险。而转成数字也可以快速地计算下一个哈希值,如12345,在子串长为4的时候,前一个是1234,那么下一个就是(1234 - 1 * 1000)* 10 + 5。
[注]:使用26进制数映射的方法会有溢出的风险,因为本题字符串长度还是比较大的。
class Solution {
public:
// 还要记录起点位置
//二分长度
int check(string& s, int len){
unordered_map<unsigned long long, int> hash_map;
unsigned long long hash_value = 0, k = 1;
int i;
for(i = 0; i < len - 1; ++ i){
k *= 26;
hash_value = hash_value * 26 + s[i] - 'a';
}
for(; i < s.size(); ++ i){
hash_value = hash_value * 26 + s[i] - 'a';
if(hash_map.find(hash_value) != hash_map.end()){
return hash_map[hash_value];
}else{
hash_map[hash_value] = i - len + 1;
hash_value -= (s[i - len + 1] - 'a') * k;
}
}
return -1;
}
string longestDupSubstring(string s) {
int l = 1, r = s.size(), m, temp_loc, len_ans = -1, loc = -1;
while(l <= r){
//cout << l << " " << r << endl;
m = (l + r) >> 1;
if((temp_loc = check(s, m)) != -1){
l = m + 1;
len_ans = m;
loc = temp_loc;
}else{
r = m - 1;
}
}
if(loc == -1) return "";
return s.substr(loc, len_ans);
}
};
思路3:据说可以用后缀数组???菜狗遁了遁了