[模版总结] - 字符串哈希robin-karp算法

模版题目

Leetcode 187. Repeated DNA Sequences

定义

常规情况下,比较两个字符串是否相同,是通过遍历两个字符串进行逐字节比较,时间复杂度为 O ( L ) \mathcal{O}(L) O(L) , 在频繁比较时会很慢,字符串哈希算法是采用将字符串逐位转化为数字,这样可以计算任意区间的substring,并在 O ( 1 ) \mathcal{O}(1) O(1)时间内完成比较。

假定一个字符串 star, 我们通过字符串哈希将 s t a r 逐位转化成一个数字:

  • n u m = s × P 3 + t × P 2 + a × P + r num=s\times P^3 + t\times P^2+a\times P+r num=s×P3+t×P2+a×P+r

Hash函数为

  • f ( s t r i n g ) = ∑ i = 1 l s [ i − 1 ] × P i − 1 f(string)=\sum_{i=1}^{l} s[i-1]\times P^{i-1} f(string)=i=1ls[i1]×Pi1

注:这里的P通常采用131 或者 31这类数字,以尽量保证不会hash冲突

算法思路

维护两个数组,一个 hash[i] 保存从开始到当前字符串位置 i 的哈希结果,一个 pow[i] 保存hash函数中的系数P的幂次结果,方便后续运算。如果要计算某一个区间[i , j]内的substring的hash值直接可以通过前缀和求差得到:

f ( s u b s t r i n g [ i , j ] ) = h a s h [ j ] − h a s h [ i − 1 ] ⋅ P j − i + 1 f(substring[i, j])=hash[j]-hash[i-1]\cdot P^{j-i+1} f(substring[i,j])=hash[j]hash[i1]Pji+1

Leetcode 187 思路

构建字符串哈希数组以及系数幂的数组,然后构建一个哈希表来记录出现过的子串,遍历过程中如果子串出现过那么就记录到最后结果中

代码如下:
using ll = unsigned long long;

class Solution {
public:
    vector<string> findRepeatedDnaSequences(string s) {
        vector<string> res;
        if (s.size()<10) return res;

        vector<ll> hash;
        vector<ll> pow;

        // init
        pow.push_back(1);
        hash.push_back(0);
        ll P = 31;
        // robin-karp
        for (int i=0; i<s.size(); i++) {
            ll val = s[i] + hash[i]*P;
            hash.push_back(val);
            pow.push_back(pow[i]*P);
        }

        
        unordered_map<ll, int> freq;
        for (int i=0; i<=s.size()-10; i++) {
            ll sub = hash[i+10]-hash[i]*pow[10];
            if (freq.count(sub)!=0 && freq[sub]==1) res.push_back(s.substr(i, 10));
            freq[sub]+=1;
        }

        return res;
    }
};

时间复杂度:哈希过程 O ( L ) \mathcal{O}(L) O(L), 后续字符串或者子串比较 O ( 1 ) \mathcal{O}(1) O(1)
空间复杂度: O ( L ) \mathcal{O}(L) O(L)

正序 & 逆向字符串哈希

字符串哈希作用是加速字符串比较,所以还有一个用途是比较回文,如果正向哈希值和逆向哈希值相同的话,那么就是回文

using ll = unsigned long long;
const int N = 20;
ll pfx[N]; // 正向哈希
ll sfx[N]; // 逆向哈希
ll p[N];

class Solution {
public:
    void robinKarp(string s) {
        //单次查看回文,双指针;多次查看,字符串哈希前缀后缀和
        // 然后dfs找到所有的组合情况
        p[0] = 1;
        ll P = 31;
        for (int i=0; i<s.size(); i++) {
            pfx[i+1] = pfx[i]*P + s[i];
            sfx[i+1] = sfx[i]*P + s[s.size()-i-1];
            p[i+1] = p[i]*P;
        }
    }
	
	bool isPalidrone(int st, int ed, string s) {
		ll forward = pfx[ed+1]-pfx[st]*p[ed-st+1];
		ll backward = sfx[s.size()-st]-sfx[s.size()-(ed+1)]*p[ed-st+1];
		return forward==backward;
	}
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值