Leetcode1044.最长重复子串的RK算法通俗理解


今天开始刷滚动哈希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;
    }
};
  1. 设定一个基数base,优先选择质数,可以降低哈希碰撞的概率(轻量场景:31;中高碰撞风险:131/1331;更大质数:911/1597);
  2. 设定一个模数mod,经典1e9+7;
  3. 对于需要考虑不同长度n的模式串,可以先预处理一下各个长度的幂次数power(base,n);
  4. 哈希表存每个哈希值的起始索引,可以验证是否是哈希碰撞
  5. 计算左出数的影响:leftContribution=(leftChar * power[k - 1]) % mod
  6. 哈希值消去左出数的影响:hashkey = (hashkey - leftContribution + mod) % mod;(防止减完变负数)
  7. 加入右入数:hashkey = (hashkey * base + rightChar) % mod;
  8. 在哈希表中寻找当前哈希值并验证:
    • hashMap.find(hashKey)!=hashMap.end()(是否找到)
    • s.substr(start, k) == s.substr(i - k + 1, k)(是否碰撞,start为find到的索引值,i-k+1为当前索引值)
  9. 存入当前索引并继续遍历

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值