M型字符串(字符串hash的简单使用)

44 篇文章 1 订阅
11 篇文章 0 订阅

如果要比较两个字符串s1 = " abc ", s2 = " abd "是否相同我们该如何判断?
从前向后遍历比较每个字符是否相同?这样的时间复杂度是O(n), 现在我们有一种更快的比较方式, 只要对字符串进行一定的预处理这样就可以实现O(1)的查询,这就是字符串hash.

如果我们能让字符串映射为唯一对应的数字那么是不是就可以实现快速比较呢?所以我们应该通过某个公式或者说函数让字符串可以转换为某一个数字,于是hash函数应运而生.

hash函数其实并没有固定的公式,一般只要能让字符串经过计算后能得到一个唯一对应的数字即可.普遍的做法是模仿进制数.
其实数字不也可以看作一串字符串么?例如一百一十六 : 116他就可以看作两个字符 ’ 1 ’ 和一个字符 ’ 6 ’ 组成的字符串.

而我们只要通过 116 = 6 * 100 + 1 * 101 + 1 * 102 (十进制转换) 就可以将字符串 " 1 1 6"映射为有唯一hash值的字符串. 当然对于其他字符也是一样的.
众所周知ASCII表如果只用来表示数字和字母的话最大的ASCII值只到127,所以127进制就足以计算一般字符串的hash值.

好了说了这么多废话其实一般我们用到的hash函数的核心代码只有一个for循环:

//Hash[0]初始化为0,字符串从下标1开始存放字符方便操作
for (int i = 1; i <= len; i++){
	Hash[i] = Hash[i - 1] * base + s[i] - 'a';
}

Hash[ i ]用来存放某一个字符串到长度为i时的hash值.base是你所采用的进制.
循环过后Hash[ len ]将存放的是整个字符串所对应的hash值.(s = “abc”)
Hash[ 1 ] = Hash[ 0 ] * base + (s[ 1 ] - ’ a ');
Hash[ 2 ] = Hash[ 0 ] * base2 + (s[ 1 ] - ’ a ') * base + (s[ 2 ] - ’ a ');
Hash[ 3 ] = Hash[ 0 ] * base3 + (s[ 1 ] - ’ a ') * base2 + (s[ 2 ] - ’ a ') * base + (s[ 3 ] - ’ a ');

什么?你说直接遍历比较不香么?下面的题就使用到了字符串hash的另一种用法。
题目链接https://ac.nowcoder.com/acm/contest/13504/A
来源:牛客网
题目描述
给一个长度为n的字符串(1<=n<=200000),他只包含小写字母
找到这个字符串多少个前缀是M形字符串.
M形字符串定义如下:
他由两个相同的回文串拼接而来,第一个回文串的结尾字符和第二个字符串的开始字符可以重叠,也就是以下都是M形字符串.
abccbaabccba(由abccba+abccba组成)
abcbaabcba(有abcba+abcba组成)
abccbabccba(由abccba+abccba组成组成,但是中间的1是共用的)
a(一个单独字符也算)

输入描述:
输入一行,一个长度为n的字符串
输出描述:
输出这个字符串有多少个前缀是M形字符串
示例1
输入
abababcabcba
输出
2
说明
a是M形串
ababa是M形串
示例2
输入
abccbaabccba
输出
2
说明
a是M形串
abccbaabccba是M形串

解决这道题首先你要知道什么是回文串(应该都知道吧),其次还要知道题中所说的M型回文串是什么?通俗的来说题中的M型回文串就是:一个字符串是回文串且它的左子串和右子串同样也是回文串。?回文串和hash有什么关系呢?首先一个字符串是回文串那么它的逆序一定是等于正序的。而它的hash值同样也满足这样的关系:如果一个字符串它的正序hash值等于它的逆序hash值那么我们就可以说明这个字符串是回文串。
既然要比较正序和逆序hash的值我们当然需要计算出逆序的hash值,这也不难:一个从前向后得到正序hash,一个从后向前即可得到逆序hash。

for (int i = 1; i <= len; i++) {
		Hash[i] = Hash[i - 1] * base + s[i] - 'a';//正序hash
		reHash[i] = reHash[i - 1] * base + s[len - i + 1] - 'a';//逆序hash
	}

现在我们求出了整个字符串的hash值,可是我们怎么得到子串(指定区间内)的hash值呢?想一想如果数字1161要你只截取下标[3,4]出来你会如何截取?显然1161-11 * 102 = 61。转换为hash表示就是:hash[4] - hash[2] * base4-2。通式就是:

ULL Hs = Hash[r] - Hash[l - 1] * base^(r-l+1);
子串的逆序hash同理:
ULL reHs = reHash[len - l + 1] - reHash[len - r] * base^(r-l+1);

接下来就是比较正序逆序是否相同即可。(题目中还要比较字符串的一半是不是回文串)
太菜了只能讲到这里了。

#include<iostream>
using namespace std;
typedef unsigned long long ULL;
#define maxn 200005
char s[maxn];
int len;
int ans = 0;
ULL base = 2333;//为什么用unsigned long long ,是因为其溢出后从零开始(0~2^64-1)不需要手动取余了
//base最好使用稍微大一些(差不多就行)的素数据说可以减少hash冲突
ULL Hash[maxn] = { 0 }, reHash[maxn] = { 0 }, power[maxn] = { 1 };
void getHash() {
	for (int i = 1; i <= len; i++) {
		power[i] = power[i - 1] * base;//进制(指数太大会溢出但由于ULL类型的特点相当于自动取余)
		//power[i]表示base^i。不使用的话就使用快速幂(会?)计算base^i
	}
	for (int i = 1; i <= len; i++) {
		Hash[i] = Hash[i - 1] * base + s[i] - 'a';//正序hash
		reHash[i] = reHash[i - 1] * base + s[len - i + 1] - 'a';//逆序hash
	}
}
bool check(int l, int r) {
	ULL Hs = Hash[r] - Hash[l - 1] * power[r - l + 1];
	ULL reHs = reHash[len - l + 1] - reHash[len - r] * power[r - l + 1];
	if (Hs == reHs) return true;//如果这一段正序逆序hash值相同说明是回文
	return false;
}
int main() {
	scanf("%s", s + 1);
	len = strlen(s + 1);
	getHash();
	for (int i = 1; i <= len; i++) {
		if (check(1, i) && check(1, (i + 1) >> 1)) {//[1 , i]是回文且它的一半也是回文说明是M型
			ans++;
		}
	}
	cout << ans << '\n';
	return 0;
}

字符串hash应用场景不是很多,能掌握就掌握(技多不压身),不然就了解了解即可。(蒟蒻菜鸡的发言)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值