例题:(1)Acwing 840.模拟散列表
开放寻址法:一般来说开所需要的数字的3倍大小,首先对该数字取模,由于有负数存在因此要用(x%N+N)%N的方式,然后找某个数字在哈希表中的位置,不断对表进行循环查找,如果找到空位就存入,因此开放寻址可能存在多个位置存储同一个数且无法记录出现次数。
#include
#include
#include
using namespace std;
const int N = 300010;
int h[N];
int null = 0x3f3f3f3f;
int find(int x){
int t = (x%N+N)%N;
while(h[t]!=null&&h[t]!=x){
t++;
if(t==N) t = 0;
}
return t;
}
void insert(int x){
int t = find(x);
h[t] = x;
}
bool query(int x){
return h[find(x)] != null;
}
int main()
{
memset(h,0x3f,sizeof(h));
int n;
scanf("%d", &n);
for(int i = 0;i
拉链法:存在多少个数就开多大的槽(头结点),每一个槽对应一个链表存放同一个哈希值的数。
#include
#include
#include
using namespace std;
const int N = 100003;
int h[N],e[N],ne[N],idx;
int find(int x){
return (x%N+N)%N;
}
void insert(int x){
int t = find(x);
e[idx] = x;
ne[idx] = h[t];
h[t] = idx++;
}
bool query(int x){
int t = find(x);
for(int i = h[t];i!=-1;i = ne[i]){
if(e[i]==x) return true;
}
return false;
}
int main()
{
memset(h, -1, sizeof(h));
int n;
scanf("%d", &n);
for (int i = 0; i < n; i ++ ){
char op[2];
scanf("%s", op);
if(op[0] == 'I'){
int x;
scanf("%d", &x);
insert(x);
}
else{
int x;
scanf("%d", &x);
if(query(x)) printf("Yes\n");
else printf("No\n");
}
}
return 0;
}
(2)AcWing 841. 字符串哈希
字符串哈希,就是把字符串看做一个p进制数进行哈希,然后利用前缀和的思想得到每个子段的哈希值。一般来说我们利用unsigned long long进行计算,这样溢出相当于对2的64次方取模,而一般来说p为131时哈希冲突不存在。
#include
#include
#include
#include
using namespace std;
typedef unsigned long long ULL;
const int N = 1e5+10,P = 131;
char str[N];
ULL h[N],p[N];
ULL get(int l,int r){
return h[r] - h[l-1] * p[r-l+1];
}
int main()
{
int n,m;
scanf("%d%d", &n, &m);
scanf("%s",str + 1);
p[0] = 1;
for(int i = 1;i<=n;i++){
h[i] = h[i-1] * P + str[i];
p[i] = p[i-1] * P;
}
for(int i = 0;i
练习:(1) P3498 KOR-Beads
思路差不多。。不过中间很多细节还是参考了下题解。。
题目就是在求每一个字符串的最多不同子串,这个“不同”要求子串正向反向都不能相同,因此可以把正向和反向各求一遍字符串hash,然后相乘。最后对每一个k的可能情况进行枚举,根据调和极数,算法复杂度必须为O(nlnn)。因此判重的复杂度需要是O(1),于是利用set。
一开始读入出现了不小的问题,之后直接改为读入数字就ac了。
#include
using namespace std;
typedef unsigned long long ull;
const int N = 2e5+20;
const ull P = 131;
ull p[N],h1[N],h2[N];
int ans[N],str[N];
int tot = 0,num,cnt,n;
set st;
ull get1(int l,int r){
return h1[r] - h1[l-1] * p[r-l+1];
}
ull get2(int l,int r){
return h2[l] - h2[r+1] * p[r-l+1];
}
int main(int argc, char** argv) {
scanf("%d",&n);
for(int i = 1;i<=n;i++){
scanf("%d",&str[i]);
}
p[0] = 1;
for(int i = 1;i<=n;i++){
h1[i] = h1[i-1] * P + str[i];
p[i] = p[i-1]*P;
}
for(int i = n;i>=1;i--){
h2[i] = h2[i+1] * P + str[i];
}
for(int k = 1;k<=n;k++){
st.clear();cnt = 0;
for(int i = k;i<=n;i+=k){
ull now = get1(i-k+1,i)*get2(i-k+1,i);
if(st.count(now)) continue;
st.insert(now);
cnt++;
}
if(cnt > tot){
tot = cnt;
num = 1;
ans[1] = k;
}
else if(cnt == tot){
ans[++num] = k;
}
}
printf("%d %d\n",tot,num);
for(int i = 1;i<=num;i++) printf("%d ",ans[i]);
return 0;
}
(2) Leetcode 202.快乐数
一点都快乐不起来。。。居然是简单。。。
已知一个数进行这个操作只可能有三种结果:(1)变得无限大(2)变为1(3)陷入循环,由于第一种情况总是会变成第二种/第三种情况(因为3位以上的数进行该操作都会变成3位数),因此第一种情况不会发生,我们只需要考虑第二和第三种情况。因此我们每进行一次操作就把得到的数放入哈希set当中,因为这种方式查询某元素是否存在的速度是O(1)的。如果目前的数已经存在则陷入循环,如果变为1则得到答案。查询可以利用contains函数。
class Solution {
public:
int happy(int x){
int sum = 0;
while(x){
sum += (x%10)*(x%10);
x/=10;
}
return sum;
}
bool isHappy(int n) {
unordered_set x;
int sum = 0;
while(1){
sum = happy(n);
if(sum==1) return true;
if(x.contains(sum)) return false;
else{
x.insert(sum);
n = sum;
}
}
}
};
(3) Leetcode 49.字母异位词分组
没想到这个方法。。。
既然是重新排列原单词,那我对每个字符串进行排序就可以得到这个字符串最原本(按照字典序)排序的字符串,然后加入到哈希表中,最后利用迭代器遍历哈希表得到每一组结果。
class Solution {
public:
vector> groupAnagrams(vector& strs) {
vector> ans;
unordered_map> x;
for(auto c:strs){
string str = c;
sort(str.begin(),str.end());
x[str].push_back(c);
}
for(unordered_map>::iterator it=x.begin();it!=x.end();it++){
ans.push_back(it->second);
}
return ans;
}
};
(4)Leetcode 350.两个数组的交集II
做出来了。。但是方法不够好。。
这里只讲哈希表的做法。利用哈希表存储每个元素在第一个数组里出现的次数,然后遍历第二个数组,假如该元素在第一个数组里出现则加入答案数组且出现次数-1,这样可以保证加入的次数一定是两个数组中出现次数较少的。
class Solution {
public:
vector intersect(vector& nums1, vector& nums2) {
vector ans;
unordered_map x;
int s1 = nums1.size(),s2 = nums2.size();
for(int i = 0;i=1){
x[nums2[i]]--;
ans.push_back(nums2[i]);
}
}
return ans;
}
};