【基础算法】字符串哈希

文章介绍了字符串前缀哈希法的核心思想,包括如何通过P进制转换和Q取模避免冲突,以及如何利用前缀哈希快速计算子串哈希。通过示例和代码解释了这种方法如何用于判断字符串子串是否完全相同,并强调了选择P和Q的重要性以减少冲突。
摘要由CSDN通过智能技术生成

🌹作者:云小逸
📝个人主页:云小逸的主页
📝Github:云小逸的Github
🤟motto:要敢于一个人默默的面对自己,强大自己才是核心。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前,其次就是现在!学会自己和解,与过去和解,努力爱自己。==希望春天来之前,我们一起面朝大海,春暖花开!==🤟
👏专栏:C++👏 👏专栏:Java语言👏👏专栏:Linux学习👏
👏专栏:C语言初阶👏👏专栏:数据结构👏👏专栏:备战蓝桥杯👏


前言

今天这篇文章讲解的是哈希的另一种题型:字符串前缀哈希法,希望我的讲解你可以喜欢,谢谢。
——————————————————————————————

字符串前缀哈希法:

核心思想:

字符串前缀哈希法是一种比较特殊的哈希方法,它是先预处理出所有字符串前缀的哈希,利用哈希数组进行存储,下标是0开始的,h[0]是等于0的。
例如:
字符串“abcabcde”
h[0]=0;
h[1]="a"的hash(哈希)值;
h[2]="ab"的hash值;
h[3]="abc"的hash值;
……

如何定义某一个前缀的哈希值?(将字符串转换为数字)

将字符串看成P进制的数,然后将P进制的数转换为十进制的数字:
在这里插入图片描述
这样转换成十进制的数字,可能非常大,因为字符串可能有10到20个,这转换后就是很大很大的数字,容易溢出和出现错误,因此可以对Q进行取模,使其映射到【0,Q-1】;

1.不可以映射成0:

如:
a----------0;
aa--------00,
这样就不对了,两个不同的字符串映射成一样的结果,造成冲突

2.会存在冲突:

将P和Q设定的非常好,就可以极大概率避免冲突(99.999%):
P=131or13331
Q=264;
这里取Q=264,这里可以直接定义哈希数组为unsigned long long ,它会使超过264的数溢出,溢出的值就等价于对264取模。

好处:利用前面算的前缀哈希可以求出任意一段的子串的哈希值。

在这里插入图片描述
如图上面的这个图:
我们已知:h[R]和h[L-1]的哈希值,怎么求出L到R的哈希值?
在这里插入图片描述
上面我们不是假设了字符串为以P为进制的数字,则右边是高位,左边是低位,
h[i]=h[i−1]×P+hash(s[i]);
故hash(s[l…r])=h[r]−h[l−1]×Pr−l+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≤105

输入样例:

8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2

输出样例:

Yes
No
Yes

代码:

#include <iostream>
#include <algorithm>

using namespace std;

typedef unsigned long long ULL;//将P和Q设定的非常好,就可以极大概率避免冲突(99.999%):
                               //P=131or13331
const int N = 100010, P = 131; //Q=2^64^;
                               //这里取Q=2^64^,这里可以直接定义哈希数组为unsigned long long ,
                               //它会使超过2^64^的数溢出,溢出的值就等价于对2^64^取模。
int n, m;
char str[N];
ULL h[N], p[N];//p[N]是存放要乘以p的多次方

ULL get(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

int main()
{
    scanf("%d%d", &n, &m);
    scanf("%s", str + 1);//因为如果直接用 scanf("%s",str); 的话,就会出现一个问题:
                         //scanf函数遇到空格或TAB,就会停下来。所以用指针的方式就可以防止这种情况发生。
                        //输入str第一个元素之后的字符串,给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;
    }

    while (m -- )
    {
        int l1, r1, l2, r2;
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);

        if (get(l1, r1) == get(l2, r2)) puts("Yes");
        else puts("No");
    }

    return 0;
}

代码解析:

1.解决冲突:

typedef unsigned long long ULL;//将P和Q设定的非常好,就可以极大概率避免冲突(99.999%):
                               //P=131or13331
const int N = 100010, P = 131; //Q=2^64^;
                               //这里取Q=2^64^,这里可以直接定义哈希数组为unsigned long long ,
                               //它会使超过2^64^的数溢出,溢出的值就等价于对2^64^取模。
int n, m;
char str[N];
ULL h[N], p[N];//p[N]是存放要乘以p的多次方

2.scanf(“%s”, str + 1);

 scanf("%s", str + 1);//因为如果直接用 scanf("%s",str); 的话,就会出现一个问题:
                         //scanf函数遇到空格或TAB,就会停下来。所以用指针的方式就可以防止这种情况发生。
                        //输入str第一个元素之后的字符串,给str[1]赋值

最后

十分感谢你可以耐着性子把它读完和我可以坚持写到这里,送几句话,对你,也对我:

1.莫言在《晚熟的人》当中说:真正的强大不是忘记,而是接受接受分道扬镳,接受世事无常,接受孤独挫败,接受突如其来的无力感,接受自己的不完美,接受困惑、不安、焦虑和遗憾,调整自己的状态,找到继续前行的力量,成为更好的自己。是的,与其苦苦的想忘记,不如坦然接受。

2.接受你最闪耀的时候,不骄傲;接受你最糟糕的时候,不气馁;接受你最平淡无奇的时候,不放弃。正如王朔对女儿说的:内心强大到混蛋,比什么都重要。

3.三毛曾说过:“给自己时间,不要焦急,一步一步来,一日一日过,请相信生命的韧性是惊人的,跟自己的心去合作,不要放弃对自己的爱护。

4.当你的能力还驾驭不了你的目标时,你就应该沉下心来历练。祛除杂念,不好高骛远,也不轻言放弃。信手拈来的从容都是厚积薄发的沉淀,找到一个准确的定位,认真打磨自己,慢慢就能变得波澜不惊,在喧嚣中宁静致远。但愿你心安,向内探求不停,做有心的蓄积者,沉淀自己,升华自己。

5.有心栽花花不开,无心插柳柳成荫。生活有时候很有意思,你越是用力证明,越感觉疲惫。越是对一件事的结果产生执念,就越反着来。太过用力,本身就是一种消耗,反而适得其反,用温柔的力量,反而能厚积薄发,就像发条上的太紧容易断,该放松时放松,该努力时努力,张弛有度才刚刚好。不骄不躁,抚平心态,才能看到更好的阳光。不要一味地追求太紧绷的用力,人生最坏的结果不过是大器晚成。

最后如果觉得我写的还不错,请不要忘记点赞✌,收藏✌,加关注✌哦(。・ω・。)

愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚菜鸟逐渐成为大佬。加油,为自己点赞!

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
下面是用哈希法实现两数之和的C语言代码: ```c #include <stdio.h> #include <stdlib.h> #define HASH_SIZE 1000 typedef struct HashNode { int key; int value; struct HashNode* next; } HashNode; typedef struct { HashNode* data[HASH_SIZE]; } HashTable; HashTable* createHashTable() { HashTable* hashTable = (HashTable*)malloc(sizeof(HashTable)); for (int i = 0; i < HASH_SIZE; i++) { hashTable->data[i] = NULL; } return hashTable; } void addNode(HashTable* hashTable, int key, int value) { int hash = abs(key) % HASH_SIZE; HashNode* node = hashTable->data[hash]; while (node != NULL) { if (node->key == key) { node->value = value; return; } node = node->next; } HashNode* newNode = (HashNode*)malloc(sizeof(HashNode)); newNode->key = key; newNode->value = value; newNode->next = hashTable->data[hash]; hashTable->data[hash] = newNode; } int getValue(HashTable* hashTable, int key) { int hash = abs(key) % HASH_SIZE; HashNode* node = hashTable->data[hash]; while (node != NULL) { if (node->key == key) { return node->value; } node = node->next; } return -1; } int* twoSum(int* nums, int numsSize, int target, int* returnSize) { HashTable* hashTable = createHashTable(); for (int i = 0; i < numsSize; i++) { int complement = target - nums[i]; int index = getValue(hashTable, complement); if (index != -1) { int* result = (int*)malloc(sizeof(int) * 2); result[0] = index; result[1] = i; *returnSize = 2; return result; } addNode(hashTable, nums[i], i); } *returnSize = 0; return NULL; } int main() { int nums[] = {2, 7, 11, 15}; int target = 9; int returnSize; int* result = twoSum(nums, sizeof(nums) / sizeof(int), target, &returnSize); for (int i = 0; i < returnSize; i++) { printf("%d ", result[i]); } free(result); return 0; } ``` 注:本代码实现了哈希表的基本操作,包括创建哈希表、添加节点、获取节点值。在实现twoSum函数时,我们利用哈希表记录每个元素在数组中的下标,然后在遍历数组的同时查找哈希表中是否存在与当前元素相加等于目标值的元素,如果存在就返回它们的下标。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云小逸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值