一、理解
把任意长度的输入通过散列算法(mod)变换成固定长度的输出,把原有的数映射成一个对应的散列值。散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,可能会造成冲突。
二、存储结构
开放寻址法
思路:利用hash函数从hash数组中找到对应 应该存放x的位置(若hash表中已有x,就不用再存)。
解决冲突:当用hash函数求出来的位置上有值,就依次往后查找,找到的第一个空位就是该值的位置,如果找到了数组尾部,就从头开始。
核心代码:
static int find(int x){//找x应该存放的位置
int k = (x%N+N)%N;//保证取余是正数
while(h[k]!=x&&h[k]!=Integer.MIN_VALUE){
//如果值相同就不用在找另一个位置存放该值,退出,如果该位置为空,说明x应该放到这个位置,退出
k++;
if(k==N)k=0;
//如果找到数组的结尾,就从头开始(这里数组的长度是要插入的数的个数的2~3倍,不存在找不到空位,死循环的问题)
//例如:操作数不超过100000,最好设数组长度N为200003(最大质数)
}
return k;//返回该点应该存放的位置
}
拉链法
(形似邻接表)数组+链表
思路:利用hash函数求出该值的散列值,将该值插入以该散列值为头结点的链表中。
插入操作:
(用数组来表示单链表的方式,e[ ]存x,ne[ ]存x指针所指向的下一次值的下标,没有值就是-1)
static void insert(int x){
int k = (x%N+N)%N;//求散列值
e[idx] = x;
ne[idx] = p[k]; p[k] = idx++;
}
查找操作 :
从该值的散列值的链表的头结点的指针指向的位置p[k]开始找,若指针指向的下标小于等于0,链表结束。
static boolean find(int x){
int k = (x%N+N)%N;
for(int i=p[k];i>0;i=ne[i]){//这里条件为i>=0是因为如果当此时i的下标就是我们第一个插入的下标是0
if(e[i]==x)
return true;
// else if(i==0) return false;但是我们也可以开始时将idx设为1,就避免了此情况
}
return false;
}
三、字符串哈希方式(字符串前缀哈希法)
思路:将字符串用一个P进制的数表示,实现不同的字符串映射到不同的数字。
如何定义一个前缀的哈希值:形如字符串X1X2X3····Xn 的P进制表示为
(把任何一个字符串都映射成0~Q-1之间的一个数)
其中P取131、13331;Q取2^64;(经验值)
因为这里Q取2^64,数组应该用long来表示
例如:“abcdabc” 用h[ ]存储字符串哈希值的前缀和
注意: h[0] = 0;//不能映射成0,例如:如果a->0,则aa->0,不能区分
h[1] = "a"的哈希值
h[2] = "ab"的哈希值
.....
前缀和公式:h[i] = h[i-1]*P +s.charAt(i-1)//该字符的ASCLL码
区间和公式:s[i] = h[r] - h[l-1]*p[r-l+1]
(这里h[ ] 表示字符串的哈希值的前缀和,s是输入的字符串,p[ ] 存储的是P的幂 p[i] = P^i)
字符串哈希(题)
给定一个长度为 nn 的字符串,再给定 mm 个询问,每个询问包含四个整数 l1,r1,l2,r2l1,r1,l2,r2,请你判断 [l1,r1][l1,r1] 和 [l2,r2][l2,r2] 这两个区间所包含的字符串子串是否完全相同。
字符串中只包含大小写英文字母和数字。
输入格式
第一行包含整数 n 和 m,表示字符串长度和询问次数。
第二行包含一个长度为 n 的字符串,字符串中只包含大小写英文字母和数字。
接下来 m 行,每行包含四个整数 l1,r1,l2,r2表示一次询问所涉及的两个区间。
注意,字符串的位置从 1 开始编号。
输出格式
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes
,否则输出 No
。
每个结果占一行。
数据范围
1≤n,m≤100000
输入样例:
8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2
输出样例:
Yes
No
Yes
代码实现:
import java.util.*;
class Main{
static final int N = 100010,P=131;//P是进制数,经验值
static int n,m;
static long[] h = new long[N];//存放前缀和
static long[] p = new long[N];//存放P的多少次幂
public static void main(String[] args){
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
String st = in.nextLine();//读取换行
String s = in.next();//读入字符串
p[0] = 1;
for(int i=1;i<=n;i++){
h[i] = h[i-1]*P+s.charAt(i-1);//求前缀和
p[i] = p[i-1]*P;//因为这个题需要用到P^(r-l+1),所以设置一个表示P的幂的数组
}
while(m-->0){
int l1,r1,l2,r2;
l1 = in.nextInt();
r1 = in.nextInt();
l2 = in.nextInt();
r2 = in.nextInt();
if(get(l1,r1)==get(l2,r2)) System.out.println("Yes");
else System.out.println("No");
}
}
static long get(int l,int r){
return h[r]-h[l-1]*p[r-l+1];//求区间和
}
}