字符串哈希

目录

1.引入问题

2. 字符串哈希的步骤

2.1预处理字符串前缀的哈希值

2.2获取字符串中的任意子串的哈希值

3.代码


1.引入问题

题目描述:

给定一个长度为 n 的字符串,再给定 m个询问,每个询问包含四个整数 l1,r1,l2,r2,请你判断 [l1,r1] 和 [l2,r2]这两个区间所包含的字符串子串是否完全相同。

字符串中只包含大小写英文字母和数字。

输入格式

第一行包含整数 n 和 m,表示字符串长度和询问次数。

第二行包含一个长度为 n 的字符串,字符串中只包含大小写英文字母和数字。

接下来 m 行,每行包含四个整数 l1,r1,l2,r2,表示一次询问所涉及的两个区间。

注意,字符串的位置从 1 开始编号。

输出格式

对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤n,m≤10^5

问题分析:

问题的关键在于如何快速判断两个字符串是否相等

我们的做法是:将字符串一一映射为数字,两个数字相等,等价于字符串相等。此时一次查询的时间复杂度为O(1)。


 

2. 字符串哈希的步骤

整体思路:

将字符串转换成一个P进制的数字,相同的字符对应的P进制数字一定相同,即哈希值相同。

故求出s[l1, r1] 和 s[l2,r2]对应的字符串的哈希值h[s[l1, r1]] h[s[l2, r2]],并判断是否相等。就能得出l1, r1 l2,r2对应的字符串是否相同

2.1预处理字符串前缀的哈希值

把字符串变成一个p进制数字

对形如 X1 X2 X3 ⋯Xn−1 Xn的字符串,采用字符的ascii 码乘上 P 的次方来计算哈希值

映射公式

(X1×Pn−1+X2×Pn−2+⋯+Xn−1×P1+Xn×P0)modQ

说明:

1. modQ是为了防溢出,Q 一般为2^64

2.冲突问题:通过巧妙设置P (131 或 13331) 的值,一般可以理解为不产生冲突。 

举例1:

s为数字字符串,各个位置的值等于数字本身P等于10

s = "4321"

has[1] = s[1] = 4;

has[2] = has[1]*p + s[2] = 4*10+3 = 43;

has[3] = has[2]*p + s[3] = 43*10+2 = 432;

has[4] = has[3]*p + s[4] =432*10 + 1 =4321;

  举例1是为了我们更好的理解举例2(采用字符的ascii 码乘上 P 的次方来计算哈希值)

举例2:

s = "abca",各个位置的值等于ascii本身,P等于131

has[1] = s[1] = 97 (a);

has[2] = has[1]*p + s[2] = 97 *131 + 98 = 12,805 (ab)

has[3] = has[2]*p + s[3] = 12,805 * 131 + 99  =1,677,554 (abc);

has[4] = has[3]*p + s[4] = 1,677,554 * 131 + 97 = 219,759,671 (abca)‬;

小结:

 预处理字符串前缀的哈希值的模板:

for (int i = 1; i <= n; i++) {
      has[i] = has[i - 1] * p + str.charAt(i - 1);
 }

2.2获取字符串中的任意子串的哈希值

思想:前缀和思想

引用举例1:s为数字字符串,各个位置的值等于数字本身P等于10

s = "4321"

求:has[s[3,4]] 意思是求"21"的哈希值

has[s[3,4]] = has[4] - has[2]*p^2 = 4321 - 43*100 = 21;

 引用举例2:s = "abca",各个位置的值等于ascii本身,P等于131

求:has[s[3,4]] 意思是求"ca"的哈希值

has[s[3,4]] = has[4] - has[2]*p^2 

 

 

小结:

获取字符串中的任意子串的哈希值的模板:

 private static long get_has(int l, int r) {
        return has[r] - has[l - 1] * mul[r - l + 1];
    }

其中mul[r - l + 1]为P^(r-l+1);

故也要提前预处理,可以在 预处理字符串前缀的哈希值的时候一起处理。


3.代码

import java.util.*;

public class Main {
    //    第一行包含整数 n和 m,表示字符串长度和询问次数。
//    第二行包含一个长度为 n的字符串,字符串中只包含大小写英文字母和数字。
//    接下来 m行,每行包含四个整数 l1,r1,l2,r2,表示一次询问所涉及的两个区间。
//    注意,字符串的位置从 1开始编号。
    static int N = 100010, p = 131;
    static long[] has = new long[N];
    static long[] mul = new long[N];

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int m = in.nextInt();
        String s = in.next();
        mul[0] = 1;//P^0=1
        //预处理字符串前缀的哈希值
        for (int i = 1; i <= n; i++) {
            has[i] = has[i - 1] * p + s.charAt(i - 1);
            mul[i] = mul[i - 1] * p;
        }
        while (m-- > 0) {
            int l1 = in.nextInt();
            int r1 = in.nextInt();
            int l2 = in.nextInt();
            int r2 = in.nextInt();
            //获取字符串中的任意子串的哈希值
            if (get_has(l1, r1) == get_has(l2, r2)) {
                System.out.println("Yes");
            } else {
                System.out.println("No");
            }
        }
    }

    private static long get_has(int l, int r) {
        return has[r] - has[l - 1] * mul[r - l + 1];
    }
}

以上为我个人的小分享,如有问题,欢迎讨论!!! 

都看到这了,不如关注一下,给个免费的赞 

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值