今天开始刷滚动哈希1044.最长重复子串时,在豆包的帮助下逐渐理解了RK算法,发现好像也不是这么难,有感而发,贴一下我的理解。
给你一个字符串 s ,考虑其所有 重复子串 :即 s 的(连续)子串,在 s 中出现 2 次或更多次。这些出现之间可能存在重叠。
返回 任意一个 可能具有最长长度的重复子串。如果 s 不含重复子串,那么答案为 “” 。
class Solution {
const int base = 31; // 基数
const int mod = 1e9 + 7; // 模数
public:
string longestDupSubstring(string s) {
int n = s.length();
int left = 1, right = n - 1;
string res;
vector<long long> power(n);
power[0] = 1;
for (int i = 1; i < n; ++i) {
power[i] = (power[i - 1] * base) % mod; // 预处理幂次数组
}
while (left <= right) { // 二分查找
int mid = left + (right - left) / 2;
if (RK(mid, s, res, power)) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return res;
}
private:
bool RK(int k, const string& s, string& res,
const vector<long long>& power) {
int n = s.length();
unordered_map<long long, int> hashMap;
long long hashkey = 0;
for (int i = 0; i < k; ++i) {
int charVal = s[i] - 'a' + 1; // 映射到1~26
hashkey = (hashkey * base + charVal) % mod;
}
hashMap[hashkey] = 0;
for (int i = k; i < n; ++i) {
int leftChar = s[i - k] - 'a' + 1;
long long leftContribution = (leftChar * power[k - 1]) % mod;
hashkey = (hashkey - leftContribution + mod) % mod; // 消除左影响
int currChar = s[i] - 'a' + 1;
hashkey = (hashkey * base + currChar) % mod; // 加入右影响
auto it = hashMap.find(hashkey);
if (it != hashMap.end()) {
int start = it->second;
if (s.substr(start, k) ==
s.substr(i - k + 1, k)) { // 验证哈希碰撞
res = s.substr(start, k);
return true;
}
}
hashMap[hashkey] = i - k + 1;
}
return false;
}
};
- 设定一个基数base,优先选择质数,可以降低哈希碰撞的概率(轻量场景:31;中高碰撞风险:131/1331;更大质数:911/1597);
- 设定一个模数mod,经典1e9+7;
- 对于需要考虑不同长度n的模式串,可以先预处理一下各个长度的幂次数power(base,n);
- 哈希表存每个哈希值的起始索引,可以验证是否是哈希碰撞
- 计算左出数的影响:
leftContribution=(leftChar * power[k - 1]) % mod - 哈希值消去左出数的影响:
hashkey = (hashkey - leftContribution + mod) % mod;(防止减完变负数) - 加入右入数:
hashkey = (hashkey * base + rightChar) % mod; - 在哈希表中寻找当前哈希值并验证:
hashMap.find(hashKey)!=hashMap.end()(是否找到)s.substr(start, k) == s.substr(i - k + 1, k)(是否碰撞,start为find到的索引值,i-k+1为当前索引值)
- 存入当前索引并继续遍历

被折叠的 条评论
为什么被折叠?



