字符串哈希算法

一、字符串哈希:将一串字符串映射成一个整数,并用它来代替字符串进行比较。这样俩个字符串的比较就变成俩个整数的比较,可以将时间复杂度减少至O(1)

二、哈希函数:为了将字符串转化为整数,需要一个哈希函数hash,使得以下条件成立:如果字符串s == t 那么 hash(s) == hash(t)。一般情况下采用多项式 哈希函数构造:

对于一个长度为n的字符串s的hash值计算:

hash(s[1..n])=s[1]pn1+s[2]pn2+s[3]pn3+...+s[i]pni+s[n]modem=i=1ns[i]pnimodm

为了减少哈希冲突,p需要选择一个素数,m需要选择一个足够大的数,因为俩个随机字符串碰撞的概率大约是1/m,这里使m=109+7

三、实现

long long compute_hash(string const& s) {
	const int m = 1e9+7;
	const int p = 1333; //如果出现冲突,p需要取更大的素数再测试
	long long has_val = 0;
	for(char c : s) {
		has_val = (has_val*p + c - 'a') % m;
	}
	return has_val;
}

四、快速计算子字符串哈希

单次计算一个字符串的哈希值复杂度是O(n),其中n为串长度,按照多项式哈希公式,假设子字符串s[l,r],那么

hash(s[l,r])=s[l]prl+s[l+1]prl1+...+s[r1]p+s[r]hash(s[l,r])=s[1]pr1+s[2]pr2+s[l1]prl+1+s[l]prl+...+s[r](s[1]pl1+s[2]pl2+...+s[l1])prl+1=i=1rs[1]prii=1l1s[1]priprl+1=hash[1,r]hash[1,l1]prl+1

使用前缀数组h[i]hash[1,i]的值,以及使用p[i] 记录pi的值,那么上面的表达式可以由以下公式表达,计算子字符串的哈希值时间复杂度减少至O(1)

hash(s[l,r])=h[r]h[l1]p[rl+1]

五、应用题目

class Solution {
public:
    vector<string> findRepeatedDnaSequences(string s) {
        int n = s.size();
        vector<long long> p(n+1);
        vector<long long> h(n+1);
        const int base = 123, mod = 1e+9;//实际测试base=123能过
        p[0] = 1;
        //构建字符串哈希前缀
        for(int i = 1; i <= n; i++) {
            h[i] = (h[i-1]*base + s[i-1]) % mod;
            p[i] = (p[i-1]*base) % mod;
        }
        int len = 10;
        unordered_map<long long, int> hash_count;
        vector<string> ans;
        for(int i = 1; i + len - 1 <= n; i++)
        {
            int j = i + len - 1;
            int t = (h[j] - h[i-1]*p[j-i+1]) % mod;
            int hash = (t + mod) % mod;
            if(hash_count.count(hash) && hash_count[hash] == 1) {
                ans.emplace_back(s.substr(i-1, len));
            }
            hash_count[hash]++;
        }
        return ans;
    }
};
	

六、哈希冲突和自然溢出

  • 第i次进行hash求值的时候,有Mi/M的概率不会发生碰撞,由此可以知道M的值越大,越不容易发生冲突
  • 自然溢出法:利用了unsigned long long 这一基本类型在溢出的时候对2641取模这一性质达到了取模的效果
  • 187. 重复的DNA序列 自然溢出解法
class Solution {
public:
    vector<string> findRepeatedDnaSequences(string s) {
        int n = s.size();
        vector<unsigned long long> p(n+1);
        vector<unsigned long long> h(n+1);
        int base = 13333; //当发生冲突时,选取更大素数尝试
        p[0] = 1;
        for(int i = 1; i <= n; i++) {
            p[i] = p[i-1] * base;
            h[i] = h[i-1] * base + s[i-1];
        }

        unordered_map<unsigned long long, int> hash_count;
        vector<string> ans;
        int len = 10;
        for(int i = 1; i + len - 1 <= n; i++) {
            int j = i + len - 1;
            unsigned long long hash = h[j] - h[i-1]*p[j-i+1];
            if(hash_count.count(hash) && hash_count[hash] == 1) {
                ans.emplace_back(s.substr(i-1, len));
            }
            hash_count[hash]++;
        }
        return ans;
    }
};

七、其他题目练习

2156. 查找给定哈希值的子串

posted @   橙皮^-^  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示