字符串哈希
字符串哈希
哈希函数的基本性质:
1)输入参数的可能性是无限的,输出的值范围相对有限
2)输入同样的样本一定得到同样的输出值,也就是哈希函数没有任何随机机制
3)输入不同的样本也可能得到同样的输出值,此时叫哈希碰撞
4)输入大量不同的样本,得到的大量输出值,会几乎均匀的分布在整个输出域上
-
base 可以选择一些质数比如:433、499、599、1000000007,也可以选择已经被证实了很好用的值:31、131、1313、13131、131313 等。
-
转化时让每一位的值从1开始,不从0开始。
如何快速得到字符串中任意子串的哈希值:
1)选择一个质数做进制数,base
2)得到 base 的各种次方,在自然溢出下的结果,用 pow 数组记录
3)得到每个位置的 hash[i],hash[i] = hash[i-1] * base + s[i] - 'a' + 1
4)子串 s[l...r] 的哈希值 = hash[r] - hash[l-1] * base 的 (r-l+1) 次方
P3370 【模板】字符串哈希
#include <vector> #include <string> #include <iostream> #include <algorithm> using namespace std; int base = 499; // 数字 + 大写 + 小写 // [0, 9] 映射成 1~10 // [A, Z] 映射成 11~36 // [a, z] 映射成 37~62 long v(char ch) { if (ch >= '0' && ch <= '9') { return ch - '0' + 1; } else if (ch >= 'A' && ch <= 'Z') { return ch - 'A' + 11; } else { return ch - 'a' + 37; } } // 计算哈希值:字符串按照 base 进制转换成数字 long value(string s) { long res = 0; for (int i = 0; i < s.length(); ++i) res = res * base + v(s[i]); return res; } int main() { int n; cin >> n; vector<long> nums(n); string str; // 计算哈希值后存入数组 for (int i = 0; i < n; ++i) { cin >> str; nums[i] = value(str); } // 统计不同数字的种数 sort(nums.begin(), nums.end()); int res = 1; for (int i = 1; i < n; ++i) if (nums[i] != nums[i - 1]) res++; cout << res; }
独特子串的数量
给你一个由数字组成的字符串 s,返回 s 中独特子字符串数量,其中的每一个数字出现的频率都相同
#include <vector> #include <iostream> #include <unordered_set> using namespace std; int equalDigitFrequency(string str) { int n = str.length(); long base = 499; // 存放字符串的哈希值 unordered_set<long> st; // 记录字符出现频率 vector<int> freq(10); for (int i = 0; i < n; i++) { // 重置 freq.clear(); freq.resize(10, 0); long hashCode = 0; // 最大出现次数 int maxCnt = 0; // 最大出现次数的字符种数 int maxCntKinds = 0; // 字符种数 int allKinds = 0; for (int j = i; j < n; j++) { int cur = str[j] - '0'; // 从 1 开始:cur + 1;而不是从零开始:cur hashCode = hashCode * base + cur + 1; freq[cur]++; // 新增一种字符 if (freq[cur] == 1) allKinds++; if (freq[cur] > maxCnt) { // 出现频率更大的字符 maxCnt = freq[cur]; maxCntKinds = 1; } else if (freq[cur] == maxCnt) { maxCntKinds++; } if (maxCntKinds == allKinds) st.emplace(hashCode); } } return st.size(); }
28. 找出字符串中第一个匹配项的下标
#include <vector> #include <iostream> using namespace std; #define ull unsigned long long class Solution { public: int MAXN = 100005; int base = 499; vector<ull> pow; vector<ull> hash; void build(string s, int n) { pow.resize(MAXN); pow[0] = 1; for (int i = 1; i < n; i++) pow[i] = pow[i - 1] * base; hash.resize(MAXN); hash[0] = s[0] - 'a' + 1; for (int i = 1; i < n; i++) hash[i] = hash[i - 1] * base + s[i] - 'a' + 1; } // 返回 s[l, r] 的哈希值 ull hashCode(int l, int r) { return l > 0 ? (hash[r] - hash[l - 1] * pow[r - l + 1]) : hash[r]; } int strStr(string haystack, string needle) { int n = haystack.length(); int m = needle.length(); build(haystack, n); ull target = needle[0] - 'a' + 1; for (int i = 1; i < m; i++) target = target * base + needle[i] - 'a' + 1; for (int l = 0, r = m - 1; r < n; l++, r++) if (hashCode(l, r) == target) return l; return -1; } };
686. 重复叠加字符串匹配
#include <vector> #include <iostream> using namespace std; #define ull unsigned long long class Solution { public: int MAXN = 30001; int base = 499; vector<ull> pow; vector<ull> hash; void build(string s) { int n = s.length(); pow.resize(MAXN); pow[0] = 1; for (int i = 1; i < n; ++i) pow[i] = pow[i - 1] * base; hash.resize(MAXN); hash[0] = s[0] - 'a' + 1; for (int i = 1; i < n; ++i) hash[i] = hash[i - 1] * base + s[i] - 'a' + 1; } ull hashCode(int l, int r) { return l > 0 ? hash[r] - hash[l - 1] * pow[r - l + 1] : hash[r]; } int repeatedStringMatch(string a, string b) { int n = a.length(); int m = b.length(); // m / n 向上取整 int k = (m + n - 1) / n; string str = ""; // a 重复 k + 1 次 for (int i = 0; i <= k; ++i) str.append(a); // 计算目标字符串的哈希值 ull target = b[0] - 'a' + 1; for (int i = 1; i < m; ++i) target = target * base + b[i] - 'a' + 1; build(str); for (int i = 0; i < n * k; ++i) if (hashCode(i, i + m - 1) == target) // 判断最有一个字符有没有到最后一个 a 上 return i + m - 1 >= n * k ? k + 1 : k; return -1; } };
30. 串联所有单词的子串
#include <vector> #include <iostream> #include <unordered_map> using namespace std; #define ull unsigned long long class Solution { public: int base = 499; vector<ull> pow; vector<ull> hash; void build(string s) { int n = s.length(); pow.resize(n); pow[0] = 1; for (int i = 1; i < n; ++i) pow[i] = pow[i - 1] * base; hash.resize(n); hash[0] = s[0] - 'a' + 1; for (int i = 1; i < n; ++i) hash[i] = hash[i - 1] * base + s[i] - 'a' + 1; } // 范围是 s[l, r] ull hashCode(int l, int r) { return l > 0 ? hash[r] - hash[l - 1] * pow[r - l + 1] : hash[r]; } // 计算一个字符串的哈希值 ull hashCode(string s) { if (s == "") return 0; int n = s.length(); ull res = s[0] - 'a' + 1; for (int i = 1; i < n; ++i) res = res * base + s[i] - 'a' + 1; return res; } // 如果 s 的长度为 n,words 里所有单词的总长度为 m // 时间复杂度 O(n + m) vector<int> findSubstring(string s, vector<string> &words) { vector<int> res; if (s == "" || s.length() == 0 || words.size() == 0) return res; build(s); int n = s.length(); // 单词长度 int wordLen = words[0].length(); // 单词总数 int wordCount = words.size(); // 字符总数 int allLen = wordLen * wordCount; // words 的词频表 unordered_map<ull, int> wordsFreq; for (string word: words) wordsFreq[hashCode(word)]++; // 窗口的词频表 unordered_map<ull, int> windowFreq; // 分为 wordLen 组,init 是当前组的首个开头 for (int init = 0; init < wordLen && init + allLen <= n; init++) { // 缺少的单词总数 int debt = wordCount; // 建立长度为 wordCount 的窗口 for (int l = init, part = 0; part < wordCount; l += wordLen, part++) { // 窗口中每个单词长度都是 wordLen ull curHashCode = hashCode(l, l + wordLen - 1); windowFreq[curHashCode]++; if (windowFreq[curHashCode] <= wordsFreq[curHashCode]) debt--; } if (debt == 0) res.emplace_back(init); // 滑动窗口 for (int firstLeft = init, lastLeft = init + allLen; lastLeft + wordLen - 1 < n; firstLeft += wordLen, lastLeft += wordLen) { ull out = hashCode(firstLeft, firstLeft + wordLen - 1); ull in = hashCode(lastLeft, lastLeft + wordLen - 1); windowFreq[out]--; if (windowFreq[out] < wordsFreq[out]) debt++; windowFreq[in]++; if (windowFreq[in] <= wordsFreq[in]) debt--; // 每次滑动后,都判断是否已经出现所有的单词 if (debt == 0) res.emplace_back(firstLeft + wordLen); } windowFreq.clear(); } return res; } };
P3763 [TJOI2017] DNA
根据匹配定义求匹配子串的数量: 给定长为 n 的字符串 s,以及长度为 m 的字符串 p,还有一个正数 k。s' 与 s 匹配的定义为,s' 与 s 长度相同,且最多有 k 个位置字符不同,要求查找字符串 s 中有多少子串与字符串 p 匹配
#include <iostream> #include <vector> #include <string> using namespace std; #define ull unsigned long long const int MAXN = 100001; const int base = 499; vector<ull> pow; vector<ull> sHash; vector<ull> pHash; void buildPow() { pow.resize(MAXN); pow[0] = 1; for (int i = 1; i < MAXN; ++i) pow[i] = pow[i - 1] * base; sHash.resize(MAXN); pHash.resize(MAXN); } // 构建辅助数组 void buildHash(string &s, string &p) { sHash[0] = s[0] - 'a' + 1; for (int i = 1; i < s.length(); ++i) sHash[i] = sHash[i - 1] * base + s[i] - 'a' + 1; pHash[0] = p[0] - 'a' + 1; for (int i = 1; i < p.length(); ++i) pHash[i] = pHash[i - 1] * base + p[i] - 'a' + 1; } // 计算子串 [l, r] 的哈希值 ull hashCode(vector<ull> &hash, int l, int r) { return l > 0 ? hash[r] - hash[l - 1] * pow[r - l + 1] : hash[r]; } // 判断长度相同的子串 s[l1, r1] 和 p[l2, r2] 上是否有不同 bool same(int l1, int r1, int l2, int r2) { return hashCode(sHash, l1, r1) == hashCode(pHash, l2, r2); } // 判断 s[left, left + m -1] 和 p 不同的位置个数是不是小于等于 k bool smallerK(int left, int m, int k) { int diff = 0; int l1 = left; int r1 = left + m - 1; int mid1; int end1 = r1; int l2 = 0; int r2 = m - 1; int mid2; int end2 = r2; while (l2 <= r2 && diff <= k) { while (l2 <= r2) { mid1 = l1 + ((r1 - l1) >> 1); mid2 = l2 + ((r2 - l2) >> 1); if (same(l1, mid1, l2, mid2)) { // s[l1, mid1] 和 p[l2, mid2] 相同,就在右侧尝试找第一个不同的位置 l1 = mid1 + 1; l2 = mid2 + 1; } else { // s[l1, mid1] 和 p[l2, mid2] 不同,就在左侧找第一个不同的位置 r1 = mid1 - 1; r2 = mid2 - 1; } } // 结束时 l2 = r2 + 1,l2 左边都是相等的,如果 l2 为 m 说明没有不同的位置了 if (l2 == m) break; // 找到了第一个不同的位置,且位置是 l1,l2 diff++; // 就在右侧区域 [l1 + 1, end1] 和 [l2 + 1, end2] 找第二个不同的位置 l1++; l2++; r1 = end1; r2 = end2; } return diff <= k; } int solution(string &s, string &p, int k) { int n = s.length(); int m = p.length(); if (n < m) return 0; buildHash(s, p); int res = 0; for (int i = 0; i <= n - m; i++) if (smallerK(i, m, k)) res++; return res; } int main() { int n; cin >> n; buildPow(); for (int i = 0; i < n; ++i) { string s, p; cin >> s >> p; cout << solution(s, p, 3) << endl; } }
本文作者:n1ce2cv
本文链接:https://www.cnblogs.com/sprinining/p/18468030
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步