# 字串演算法
## Rolling Hash 演算法
- [Leetcode 214 Shortest Palindrome](https://leetcode.com/problems/shortest-palindrome/)
- 時間複雜度為 $O(n)$
- `s="bcd"`
- 正著算:2->23->234
- 倒著算:2->32->432
```c++=
long long ll = 0, rr = 0, base = 1;
for(int i=0;i<n;i++){
int dif = s[i] - 'a';
ll = (ll * 26 + dif) % mod;
rr = (rr + base * dif) % mod;
base = (base * 26) % mod;
}
```
## KMP 演算法
- [Leetcode 28 Strstr()](https://leetcode.com/problems/implement-strstr/)
- 比對 `t` 是否為 `s` 的子字串
- 其中 $|s|=m$ ,$|t|=n$
- 計算 `t` 的 failure function 時間複雜度為 $O(n)$
- 比對的時間複雜度為 $O(m)$
```clike=
// failure function: 對每個 prefix,找出次長相同前綴後綴長度
// AAAB -> 0 1 2 0
// AAABA -> 0 1 2 0 1
// ABABCAB -> 0 0 1 2 0 1 2
vector<int> getNext(string t){
int n = t.length();
vector<int> next(n, 0);
for(int i=1, len=0; i<n;i++){
while(len && t[i]!=t[len]) len = next[len-1];
if(t[i]==t[len]) len++;
next[i] = len;
}
return next;
}
int strStr(string s, string t) {
int m = s.length(), n = t.length();
vector<int> next = getNext(t);
for(int i=0, len=0; i<m;i++){
while(len && s[i]!=t[len]) len = next[len-1];
if(s[i]==t[len]) len++;
if(len==n) return i-len+1;
}
return -1;
}
```
## Suffix Array 演算法
- (1) 倍增演算法建構 suffix array 和 rank: `s = "banana"`
- 長度 1: `rank = "102020"`
- 長度 2: `rank = "213130"`
- 長度 4: `rank = "325140"` <-> `sa = "531042"`
```c++=
int sa[1005], rk[1005], tmp[1005], lcpa[1005];
void build_SA(string s){
int n = s.length(), gap = max(n,128);
for(int i = 0; i < n; i++) rk[i] = s[i]-'a';
for(int wide = 1; ; wide <<= 1){ // 執行 O(logn) 輪
set<int> s;
unordered_map<int,int> mp;
int idx = 0;
for(int i = 0; i < n; i++){
if(i + wide < n) s.insert(rk[i] * gap + rk[i+wide]);
else s.insert(rk[i] * gap);
}
for(auto data:s) mp[data] = idx++;
for(int i = 0; i < n; i++){
if(i + wide < n) rk[i] = mp[rk[i] * gap + rk[i+wide]];
else rk[i] = mp[rk[i] * gap];
}
if(idx == n) break;
}
for(int i = 0; i < n; i++) sa[rk[i]] = i;
}
```
-
- 把倍增法的排序改成 `counting sort` 可降低時間複雜度為 $O(nlogn)$
- 另外還有 $O(n)$ 時間複雜度的演算法 e.g. SAIS / DC3
- (2) 建構 LCP 陣列
- `LCPA[i]` : 排名第 i 名和第 i-1 名的後綴,最長共同前綴長度
- 起始位置分別為 sa[i] 和 sa[i-1]
- 從最長的後綴開始,逐次去掉開頭字元,跳著填寫 LCP Array
- 最長後綴:排名為 rk[0],比較 sa[rk[0]] 和 sa[rk[0]-1]
- 次長後綴:排名為 rk[1],比較 sa[rk[1]] 和 sa[rk[1]-1]
- ...
- `由上一格結果 -1 後,繼續往下比對`
```c++=
void build_lcp_array(string s){
int n = s.length(), lcp = 0;
for(int i = 0; i < n; i++){ // 從最長後綴開始填
if(rk[i]==0){ lcpa[0] = 0; continue;}
int ii = sa[rk[i]]; // ii = i
int jj = sa[rk[i]-1];
if(lcp > 0) lcp -= 1;
while(s[ii+lcp] == s[jj+lcp]) lcp++;
lcpa[rk[i]] = lcp;
}
}
```