模版题目
Leetcode 187. Repeated DNA Sequences
定义
常规情况下,比较两个字符串是否相同,是通过遍历两个字符串进行逐字节比较,时间复杂度为 O ( L ) \mathcal{O}(L) O(L) , 在频繁比较时会很慢,字符串哈希算法是采用将字符串逐位转化为数字,这样可以计算任意区间的substring,并在 O ( 1 ) \mathcal{O}(1) O(1)时间内完成比较。
假定一个字符串 star, 我们通过字符串哈希将 s t a r 逐位转化成一个数字:
- n u m = s × P 3 + t × P 2 + a × P + r num=s\times P^3 + t\times P^2+a\times P+r num=s×P3+t×P2+a×P+r
Hash函数为
- f ( s t r i n g ) = ∑ i = 1 l s [ i − 1 ] × P i − 1 f(string)=\sum_{i=1}^{l} s[i-1]\times P^{i-1} f(string)=∑i=1ls[i−1]×Pi−1
注:这里的P通常采用131 或者 31这类数字,以尽量保证不会hash冲突
算法思路
维护两个数组,一个 hash[i] 保存从开始到当前字符串位置 i 的哈希结果,一个 pow[i] 保存hash函数中的系数P的幂次结果,方便后续运算。如果要计算某一个区间[i , j]内的substring的hash值直接可以通过前缀和求差得到:
f ( s u b s t r i n g [ i , j ] ) = h a s h [ j ] − h a s h [ i − 1 ] ⋅ P j − i + 1 f(substring[i, j])=hash[j]-hash[i-1]\cdot P^{j-i+1} f(substring[i,j])=hash[j]−hash[i−1]⋅Pj−i+1
Leetcode 187 思路
构建字符串哈希数组以及系数幂的数组,然后构建一个哈希表来记录出现过的子串,遍历过程中如果子串出现过那么就记录到最后结果中
代码如下:
using ll = unsigned long long;
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
vector<string> res;
if (s.size()<10) return res;
vector<ll> hash;
vector<ll> pow;
// init
pow.push_back(1);
hash.push_back(0);
ll P = 31;
// robin-karp
for (int i=0; i<s.size(); i++) {
ll val = s[i] + hash[i]*P;
hash.push_back(val);
pow.push_back(pow[i]*P);
}
unordered_map<ll, int> freq;
for (int i=0; i<=s.size()-10; i++) {
ll sub = hash[i+10]-hash[i]*pow[10];
if (freq.count(sub)!=0 && freq[sub]==1) res.push_back(s.substr(i, 10));
freq[sub]+=1;
}
return res;
}
};
时间复杂度:哈希过程
O
(
L
)
\mathcal{O}(L)
O(L), 后续字符串或者子串比较
O
(
1
)
\mathcal{O}(1)
O(1)。
空间复杂度:
O
(
L
)
\mathcal{O}(L)
O(L)
正序 & 逆向字符串哈希
字符串哈希作用是加速字符串比较,所以还有一个用途是比较回文,如果正向哈希值和逆向哈希值相同的话,那么就是回文
using ll = unsigned long long;
const int N = 20;
ll pfx[N]; // 正向哈希
ll sfx[N]; // 逆向哈希
ll p[N];
class Solution {
public:
void robinKarp(string s) {
//单次查看回文,双指针;多次查看,字符串哈希前缀后缀和
// 然后dfs找到所有的组合情况
p[0] = 1;
ll P = 31;
for (int i=0; i<s.size(); i++) {
pfx[i+1] = pfx[i]*P + s[i];
sfx[i+1] = sfx[i]*P + s[s.size()-i-1];
p[i+1] = p[i]*P;
}
}
bool isPalidrone(int st, int ed, string s) {
ll forward = pfx[ed+1]-pfx[st]*p[ed-st+1];
ll backward = sfx[s.size()-st]-sfx[s.size()-(ed+1)]*p[ed-st+1];
return forward==backward;
}