//思想,把字符串映射为哈希值,通过哈希值就可以定位唯一字符串,可以某种程度上替代kmp,而且比kmp好理解好记忆
//字符串hash模板
int P = 131; // 或者13331 经验值
String s = "hello";
int n = s.length();
long[] h = new long[n + 1], p = new long[n + 1];
p[0] = 1;
//计算出模板的h,p值
for (int i = 1; i <= n; i++) {
h[i] = h[i] * P + s.charAt(i);
p[i] = p[i - 1] * P;
}
//此时任意的字符串都可以利用h,p进行映射 如L - R;
// h[L - R] = h[R] - h[L - 1] * p[R - L + 1],证明随便找几个串试试即可
//such as 寻找hello中的ll 则L = 3, R = 4
int len = 2, L = 3, R = 4;
long hash = h[4] - h[L - 1] * p[R - L + 1];//这就是ll对应的哈希值可以快速查找ll
//如果您想推导可以像上图!~
//不废话了我们来做几题试试吧!
1.先来试试替换kmp?当然面试可以写字符串哈希
//传统解法肯定是kmp,但是我们可以看到有重复串的查找,尝试字符串哈希
public int strStr(String haystack, String needle) {
if(needle == "" || haystack == "") return 0;
int P = 131;//经验值
int n = haystack.length();
//模板
int[] h = new int[n + 1], p = new int[n + 1];
p[0] = 1;
for(int i = 1; i <= n; i ++){
h[i] = h[i - 1] * P + haystack.charAt(i - 1); //把heystack中的字符串进行映射
p[i] = p[i - 1] * P;
}
int hash = 0;
for(int i = 0; i < needle.length(); i ++){
hash = hash * P + needle.charAt(i); //把needle中的值算出来,因为是计算全部所以不需要p数组
} //因为h[L - 1] = h[0] = 0后面没了
for(int i = 1; i <= n - needle.length() + 1; i ++){
//我们从左边开始遍历,容易知道L = i, R = i + needle.length() - 1,套用模板可以枚举出he,el,ll,lo,o(n)时间复杂度
int Hash = h[i + needle.length() - 1] - h[i - 1] * p[needle.length()];
if(Hash == hash) return i - 1; //注意这边映射成了1-n,所以返回的时候要下标-1
}
return -1;
}
2.什么kmp你觉得不满足?那咱们来一道字节一面二面出的概率较高的困难?我看的面经中遇到十次以上了所以来试试吧!
//先分析一下思路,最长重复串的话,那我们考虑重复可以利用字符串哈希去预处理哈希值找是否有重复的哈希值
//最长的话简单想到暴力1-2-3一个一个尝试那么会超时,那怎么样找最长?->二分咋样?
//这边给小伙伴们一个思路,通常整个数组中找最长最短且需要验证的题目一般是二分+验证可以nlogn,如果你不信可以去力扣周赛找找挺多的这种题,好啦不废话了我们开始
long P = 13331;
long[] h, p;
public String longestDupSubstring(String s) {
int n = s.length();
//模板先整起来
h = new long[n + 1]; p = new long[n + 1];
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;
}
int l = 1, r = s.length() - 1;
String ans = "";
while(l <= r){
int mid = (l + r) >> 1;
//这边就是验证最长的过程
String t = check(mid, s);
if(t != "") {
//记录答案
ans = t;
l = mid + 1;
}
else r = mid - 1;
}
return ans; //诶好像就结束了是不是有点简单?当然本题可以利用kmp,但是我也记不住kmp,忘了好几次了放弃了。
}
public String check(int len, String s){
//这边我们需要找到重复的哈希值,那就利用set来记录是否有重复,当然你也可用map
Set set = new HashSet<>();
for(int i = 1; i <= s.length() - len + 1; i ++){
//从1开始,因为我们已经二分了长度所以 R = i + len - 1, L = i - 1
long hash = h[i + len - 1] - h[i - 1] * p[len];
//举个例子len = 3,s=banana,这边就是计算出ban,ana,nan,ana的哈希值,然后放到set里面,set有重复证明重新出现过合法
if(set.contains(hash)) return s.substring(i - 1, i - 1 + len);
set.add(hash);
}
return "";
}
3.最后再来一题吧?思路更简单了你可以自己尝试一下
//思路:诶你自己肯定没去试试,好吧我带你做,这边呢就是找有没有连接字符串,那我们连接字符串拆开是不是就是两个一样的哈希值了?
//那我们可以预处理一下哈希值,然后枚举字符串(这边因为利用了字符串哈希所以比较两个字符串可以达到O(1)的复杂度)那就简单了啦~
public int distinctEchoSubstrings(String text) {
int P = 131;
int n = text.length();
Set set = new HashSet<>();
//模板
int[] h = new int[n + 1], p = new int[n + 1];
p[0] = 1;
for(int i = 1; i <= n; i ++){
h[i] = h[i - 1] * P + text.charAt(i - 1);
p[i] = p[i - 1] * P;
}
for(int i = 1; i <= n; i ++){
for(int j = i + 1; j <= n; j += 2){
//枚举分成两个
int len = (j - i + 1) / 2;
int hash1 = h[j] - h[j - len] * p[len]; //左边那一半
int hash2 = h[j - len] - h[i - 1] * p[len]; //右边那一半 相等就合法
if(hash1 == hash2 && !set.contains(hash1)){
set.add(hash1);
}
}
}
return set.size();
}
总结:1.可以代替kmp解决问题 2.模板好记忆 3.适用于重复xxxx的字符串问题
好啦今天的小讲堂就到这里啦,希望你能够把字符串哈希学会,说不定面试还能给面试官秀一手,当然最关键的还是他能够替代kmp,而且更简单记忆更好写,这个模板不难背吧?当然给你留到作业吧!力扣1392 hard,看看你能不能学以致用亲自解决一道hard呢?加油哦!