从现在开始每天至少刷一道题。
题库:lintcode
594. strStr II
题目链接
难度:hard
算法:Rabin–Karp
先普及两个知识点滚动哈希和Rabin–Karp算法
什么是滚动哈希?
首先了解一下通过哈希函数,将字符转化成数字,id(char) = char - ‘a’ + 1。 例如字符’c’可以转化成3。
滚动哈希指的是当前哈希值依赖上一个哈希值的结果作为输入。给定一个字符串s, i 为s的索引, hash(i) = (hash(i-1) * base + id(s[i]) ) % mod, 其中base, mod均为质数,且较大。核心思路就是将字符串通过哈希函数转换成数字。常常用来判断两个字符串是否相同。注意,哈希值加法和乘法运算后要对值取模,如果是减法,则要考虑负数的情况,加上mod后再取模, hash = (hash + mod) % mod.
题目1: 为什么id(char) = char - 'a' + 1
,要+1
?
题目2:给定字符串s = “abcd”,mod = 10007, base = 31, 如何计算s的哈希值。
(答案见文末)
Rabin–Karp algorithm
根据wikepedia提供的解释,给定一个souce字符串 和pattern(target)字符串,找出source首次匹配pattern的索引。这里用到了滚动哈希。先计算pattern的哈希值,然后计算source中pattern length的子字符串的哈希值,如果两个哈希值相等,接着比较两个字符串的字符是否相等。
function RabinKarp(string s[1..n], string pattern[1..m])
hpattern := hash(pattern[1..m]);
for i from 1 to n-m+1
hs := hash(s[i..i+m-1])
if hs = hpattern
if s[i..i+m-1] = pattern[1..m]
return i
return not found
解题思路
一看到这种字符串匹配的题目,就想想能不能套Rabin–Karp算法。
- 计算target的哈希值。假设target=“dec”, 哈希值就是(((d - a + 1)*base + (e-a + 1))*base + c - a + 1) % mod
- 遍历source,计算target length的子字符串的哈希值。方法就是加一个字符,减去头部的字符,维持一个固定长度的子字符串。比如,hash(“dec”) = hash(“adec”) - (‘a’ - ‘a’ + 1) * (base^targetLen)。左边最高位,右边最低位。
解法
public class Solution {
/*
* @param source: A source string
* @param target: A target string
* @return: An integer as index
*/
public int strStr2(String source, String target) {
// write your code here
if(target == null || source == null){
return -1;
}
if(target.length() == 0){
return 0;
}
if(source.length() == 0){
return -1;
}
long base = 33;
long mod = 10000007;
int tlen = target.length();
int slen = source.length();
// compute hash value of target
long targetHash = 0;
for(int i=0;i<tlen;i++){
targetHash = (targetHash * base + target.charAt(i) - 'a' + 1) % mod;
}
// compute base of target length
long baseTmp = 1;
for(int i=0;i<tlen;i++){
baseTmp = baseTmp * base % mod;
}
int ans = -1;
long sourceHash = 0;
for(int i=0;i<slen;i++){
sourceHash = (sourceHash * base + source.charAt(i) - 'a' + 1) % mod;
if (i < tlen){
continue;
}
sourceHash = (sourceHash - (source.charAt(i - tlen) - 'a' + 1) * baseTmp % mod) % mod;
sourceHash = (sourceHash + mod) % mod;
// System.out.printf("\nsourceHash=%d, targetHash=%d", sourceHash, targetHash);
if (sourceHash == targetHash){
ans = i - tlen + 1;
break;
}
}
return ans;
}
}