昨天做了一道字符串哈希的题,感觉还好理解。今天的题看了代码不知道为什么,搜来搜去发现不知道的东西还很多,网上找到的东西也都是很零散,书上也没有系统的讲解。
先自己整理一下这些零散的知识:
关于字符串涉及到的算法大概有:hash、KMP、Trie、AC自动机等等,现在还都不明白是怎么回事,这次先研究字符串hash。
有一个帖子说的比较好:所谓字符串hash,就是想办法把一个字符串变成一个整数。应用的场景一般是字符串的查重,如果要在一个大的字符串集合中查找是否有某一个字符串,常规的比较查找方法效率会很低,如果每一个字符串都能转换成一个整数(最好是唯一的整数)那就快多了。
构造一个好的hash方法,将一个字符串转换成一个整数,就是“字符串哈希算法”
步骤:
1、首先把字符串中的每一个字符转换成一个数字,比如把‘a’-‘z’转换成1-26。可以表示为 id( s[i] ) = s[i]-‘a’+1,注意一般不要把某个字符映射成‘0’,如果把‘a’映射成0,那在以后就不好区分‘b’和‘ab’了。
2、经过第1步之后,一个字符串就变成了一串数字,这串数字还要再经过处理,把它映射成一个整数。映射的方法就是把这串数字当成一个p进制的数,并把它按照b进制数转换成十进制,得到一个十进制整数,也就是构造hash函数:
unsigned long long Hash[n]
hash[i]=hash[ ]∗p+id(s[i])
这里定义成unsigned long long,说是利用unsigned long long的范围自然溢出,相当于自动对2^64−1取模,
并且,这个结果一般还要对某个数取模:
hash[i]=(hash[i−1])∗p+id(s[i]) % mod
其中p和mod均为质数,且有p<mod
对于此种Hash方法,将p和mod尽量取大即可,这种情况下,冲突的概率是很低的
p 一般可以取:131、13331
举个例子,一个字符串‘abc’: s[ ]= {‘abc’},将其转换为数字串‘123’
取p为13、mod为101
则:
hash[0] = 1
hash[1] = hash[0]*13 + 2 = 15
hash[2] = hash[1]*13 +3 = 97
这样,字符串’abc’就映射成了一个整数97了,
3、接下来很重要的一点:当得到了一个字符串的hash值之后,如何计算它的某个子串的hash值,这也得需要一系列的推导:
假设有一个长度为5的字符串s: |s|=5,hash函数为:hash[i]=hash[ i-1
]∗p+s[i],则有:
子串‘s1’------------------hash[1]=s1
子串‘s1s2’---------------hash[2]=s1∗p+s2
子串‘s1s2s3’----------- hash[3]=s1∗p2+s2∗p+s3
子串‘s1s2s3s4’-------- hash[4]=s1∗p3+s2∗p2+s3∗p+s4
子串‘s1s2s3s4s5’----- hash[5]=s1∗p4+ s2∗p3+s3∗p2+s4∗p+s5
现在想计算子串‘s3s4’的hash值,子串‘s3s4’可以看做是子串‘s1s2s3s4’-‘s1s2’,那是不是就可以通过‘s1s2s3s4’的hash值减’s1s2’的hash值得到呢?下面就来推导:
对于字符串‘s3s4’,按公式其hash值= s3*p+s4
对比一下 hash[4]和hash[2],可以发现 hash[4]-hash[2]p2就是s3p+s4,这样子串‘s3s4’的hash值就可以通过hash[4]和hash[2]的运算得到,那么上面相减的计算中hash[2]为什么要乘以p的2次方,而不是3次方,其他次方呢? 规律就是 ‘s3s4’ 就是(4-3+1)次方
下面推出一般情况:
对于一个长度为n的字符串 s1s2s3s4……sn,已知每一个hash[i],(1 ≤ i ≤ n)
要计算其中某段长度子串的hash值,子串是‘sl……sr’,且 (1 ≤ l ≤ r ≤ n),其公式如下:
hash[sl…sr] = hash[r] - hash[l-1] * p^( r-l+1)^
如果需要取模的话,hash[sl…sr] = (hash[r] - hash[l-1] * p^( r-l+1)^)%mod
几种应用场景:
1
给两个字符串S1,S2,求S2是否是S1的子串,并求S2在S1中出现的次数
把S2 Hash出来,在S1里找所有长度为|S2|并计算Hash,放入map中查询
2
给N个单词串,和一个文章串,求每个单词串是否是文章串的子串,并求每个单词在文章中出现的次数
把每一个单词hash成整数,再把文章的每一个子串hash成整数,接下来只需要进行整数上的查找即可。
复杂度:O(|A|2+|S|)O(|A|2+|S|)
用AC自动机可以做到O(|A|+|S|)
3
给两个字符串S1,S2,求它们的最长公共子串的长度
将S1的每一个子串都hash成一个整数,将S2的每一个子串都hash成一个整数。两堆整数,相同的配对,并且找到所表示的字符串长度最大的即可
4
给一个字符串S,求S的最长回文子串。
比如abcbbabbc的最长回文子串是cbbabbc,bbabb也是回文串,但不是最长的
先求子串长度位奇数的,再求偶数的。枚举回文子串的中心位置,然后二分子串的长度,直到找到一个该位置的最长回文子串,不断维护长度最大值即可。
复杂度:O(|S|∗log|S|)