拉宾-卡普算法详解以及示例[rabin-karp]

0 拉宾-卡普算法

来自wiki
在计算机科学中,拉宾-卡普算法(英語:Rabin–Karp algorithm)或卡普-拉宾算法(Karp–Rabin algorithm),是一种由理查德·卡普与迈克尔·拉宾于1987年提出的、使用散列函数以在文本中搜寻单个模式串的字符串搜索算法单次匹配。该算法先使用旋转哈希以快速筛出无法与给定串匹配的文本位置,此后对剩余位置能否成功匹配进行检验。此算法可推广到用于在文本搜寻单个模式串的所有匹配或在文本中搜寻多个模式串的匹配。

1 算法本身主要思想

  • 1 朴素匹配: text = "abcdefabc"长度为n,去匹配长度为m的模式串abc,
    • 具体做法则是找出所有长度为m的字串,然后为每一个m字串去匹配模式串abc,复杂度为O(nm)
  • 2 使用拉宾-卡普算法改进:
    • 2.1 找出了所有长度为m的字串,那么能不能在O(1)的时间内去判断两个长度为m的字串是否相等?
    • 2.2 很自然的想到hash,那么如何计算一个字串的hash?比如abc,使用26进制编码(因为text中只有小写字母)即可: hash(abc) = 26^2 * (‘a’ - ‘a’) + 26^1 * (‘b’ - ‘a’) + 26^0 * (‘c’ - ‘a’);
    • 2.3 很容易注意到上面,若字符串很长,比如100,那么hash值就有26^100,显然太大,需要取模,取模后带来问题,造成hash碰撞,则需要散列,常用的有拉宾指纹,我们可以使用两个不同模算出来一对hash值当做为一个整体hash,降低hash碰撞的概率
    • 2.4 那么计算hash明明需要读取模式串,复杂度为O(m)啊?
      • 那是因为对于text,我们只需要计算第一个长度为m的字串的hash,后面的字串hash都可以通过O(1)时间获取:
      • 直接看图:o(1)算hash图片来源
      • 解释: ft_i为第i个字串的hash,在o(1)时间内得到ft_i+1的方案就如图所示,或者看如下例子的代码的check函数

例子

1044. 最长重复子串 longestDupSubstring

1 题目

https://leetcode-cn.com/problems/longest-duplicate-substring/

2 解题思路


typedef pair<long long, long long> pll;
class Solution {
public:
    static constexpr int big = 1000000006;
    // cal: a^m % mod, when m = 1000, a = 26, there will be overflow
    long long pow(int a, int m, int mod) {
        long long ans = 1;
        long long curNum = a;
        while(m > 0) {
            if(m % 2 == 1) {
                ans = ans * curNum % mod;       
                // overflow
                if(ans < 0) { 
                    ans += mod;
                }
            }
            curNum = curNum * curNum % mod;
            // overflow
            if(curNum < 0) {
                curNum += mod;
            }
            m /= 2;
        }
        return ans;
    }

    // return st of substr with len = len
    int check(vector<int>& arr, int len, int a1, int a2, int mod1, int mod2) {
        int n = arr.size();
        long long powA1 = pow(a1, len, mod1);
        long long powA2 = pow(a2, len, mod2);
        long long hashA1 = 0, hashA2 = 0;

        // cout << "d2.5" << endl;
        // cal hash of the first substr
        // hashA1 = arr[0] * a1^(len-1) + arr[1] * a1^(len-2) + ... + arr[len-1]
        for(int i = 0; i < len; ++i) {
            hashA1 = (hashA1 * a1 % mod1 + arr[i]) % mod1;
            hashA2 = (hashA2 * a2 % mod2 + arr[i]) % mod2;
            hashA1 += hashA1 >= 0 ? 0 : mod1;
            hashA2 += hashA2 >= 0 ? 0 : mod2;
        }

        // cout << "d3" << endl;
        // calculate all substr's hash with len = len
        set<pll> seen;
        seen.emplace(hashA1, hashA2);
        for(int st = 1; st <= n - len; ++st) {
            // cout << "d4" << endl;
            // O(1) to cal next hash
            hashA1 = (hashA1 * a1 % mod1 - arr[st - 1] * powA1 % mod1 + arr[st + len - 1]) % mod1;
            hashA2 = (hashA2 * a2 % mod2 - arr[st - 1] * powA2 % mod2 + arr[st + len - 1]) % mod2;
            hashA1 += hashA1 >= 0 ? 0 : mod1;
            hashA2 += hashA2 >= 0 ? 0 : mod2;
            // before cursubstr, there is a same one
            if(seen.count(make_pair(hashA1, hashA2))) {
                return st;
            }
            seen.emplace(hashA1, hashA2);
        }

        return -1;
    }

    string longestDupSubstring(string s) {
        int n = s.size();

        // code the string
        vector<int> arr(n);
        for(int i = 0; i < n; ++i) {
            arr[i] = s[i] - 'a';
        }

        // two random base and mod
        srand((unsigned)time(NULL));
        int a1 = random()%75 + 26;
        int a2 = random()%75 + 26;
        int mod1 = random()%(INT_MAX - big) + big;
        int mod2 = random()%(INT_MAX - big) + big;
        
        // bin search the length of longest dup substr
        int l = 1, r = n - 1;
        int finalSt = -1, finalLen = -1;
        while(l <= r) {
            // m represents target len
            // int m = (l + r) / 2; 
            int m = l + (r - l + 1) / 2; 
            // cout << "d1" << endl;
            int st = check(arr, m, a1, a2, mod1, mod2);
            // cout << "d2" << endl;
            if(st != -1) {
                finalLen = m;
                l = m + 1;
                finalSt = st;
            } else {
                r = m - 1;
            }
        }
        return finalLen == -1 ? "" : s.substr(finalSt, finalLen);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值