字符串哈希
哈希函数的基本性质:
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;
}
}
标签:hash,int,pow,++,base,l2,哈希,字符串
From: https://www.cnblogs.com/sprinining/p/18468030