pi[i] = 字串 s[0..i] 的最長真前綴,且同時是其後綴的長度。
例:s = "ababaca"
pi = [0,0,1,2,3,0,1](常見教科書版本;也有以 1-based 表示者)
j = pi[i-1]
while (j > 0 && s[i] != s[j]) j = pi[j-1];
if (s[i] == s[j]) j++;
pi[i] = j;
直觀理解:嘗試延伸目前匹配長度 j,失敗就「回退」到更短的 border 長度 pi[j-1] 再試。
同時是前綴與後綴的子字串。所有 border 長度可由 pi[n-1] 一路沿 pi[len-1] 追溯。
原題:
https://cses.fi/problemset/task/1753
題意:
給字串 T(text)與 P(pattern),求 P 在 T 中出現的次數(允許重疊)。
解題思路:
#include <bits/stdc++.h>
using namespace std;
vector<int> prefix_function(const string& s){
int n = (int)s.size();
vector<int> pi(n);
for(int i=1;i<n;i++){
int j = pi[i-1];
while(j>0 && s[i]!=s[j]) j = pi[j-1];
if(s[i]==s[j]) j++;
pi[i]=j;
}
return pi;
}
int main(){
ios::sync_with_stdio(false); cin.tie(nullptr);
string T, P;
if(!(cin >> T >> P)) return 0;
auto pi = prefix_function(P);
int j = 0, ans = 0;
for(char c: T){
while(j>0 && c!=P[j]) j = pi[j-1];
if(c==P[j]) j++;
if(j==(int)P.size()){
ans++;
j = pi[j-1]; // 允許重疊
}
}
cout << ans << "\n";
return 0;
}
原題:
https://cses.fi/problemset/task/1732
題意:
給字串 s,輸出所有 border 長度(即同時為 s 的前綴與後綴的非空子字串長度),由小到大。若沒有,輸出空行。
解題思路:
#include <bits/stdc++.h>
using namespace std;
vector<int> prefix_function(const string& s){
int n=(int)s.size(); vector<int> pi(n);
for(int i=1;i<n;i++){
int j=pi[i-1];
while(j>0 && s[i]!=s[j]) j=pi[j-1];
if(s[i]==s[j]) j++;
pi[i]=j;
}
return pi;
}
int main(){
ios::sync_with_stdio(false); cin.tie(nullptr);
string s; if(!(cin>>s)) return 0;
auto pi = prefix_function(s);
vector<int> lens;
for(int len = pi.back(); len > 0; len = pi[len-1]) lens.push_back(len);
sort(lens.begin(), lens.end());
for(size_t i=0;i<lens.size();i++){
if(i) cout << ' ';
cout << lens[i];
}
cout << "\n";
return 0;
}
原題:
https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/description/
題意:
回傳 needle 在 haystack 中第一次出現的起始索引;若不存在回傳 -1。空字串按題意通常回傳 0。
解題思路:
經典 KMP:對 needle 建 π,掃 haystack 尋找第一個 j == |needle| 的位置即可。
class Solution {
vector<int> prefix_function(const string& s){
int n=s.size(); vector<int> pi(n);
for(int i=1;i<n;i++){
int j=pi[i-1];
while(j>0 && s[i]!=s[j]) j=pi[j-1];
if(s[i]==s[j]) j++;
pi[i]=j;
}
return pi;
}
public:
int strStr(string haystack, string needle) {
if(needle.empty()) return 0;
auto pi = prefix_function(needle);
int j=0;
for(int i=0;i<(int)haystack.size();i++){
while(j>0 && haystack[i]!=needle[j]) j=pi[j-1];
if(haystack[i]==needle[j]) j++;
if(j==(int)needle.size()){
return i - j + 1;
}
}
return -1;
}
};
原題:
https://leetcode.com/problems/longest-happy-prefix/
題意:
給字串 s,找出最長的非空字串,使它同時是 s 的前綴與後綴(不可等於整個字串),回傳該字串。
解題思路:
class Solution {
vector<int> prefix_function(const string& s){
int n=s.size(); vector<int> pi(n);
for(int i=1;i<n;i++){
int j=pi[i-1];
while(j>0 && s[i]!=s[j]) j=pi[j-1];
if(s[i]==s[j]) j++;
pi[i]=j;
}
return pi;
}
public:
string longestPrefix(string s) {
auto pi = prefix_function(s);
int len = pi.back();
return s.substr(0, len);
}
};