有些题目需要频繁的判断任意子串是否是回文的。本文分享一下利用马拉车的长度数组判断任意子串是否回文的方法,时间复杂度低至 O(1) 。
先来回忆一下马拉车:
当我们要判断 S[L:R]是否为回文串时,仅需两个步骤:
以 S[1:5] 为例,首选换算对应子串位置,得到 PS[3:11],找到PS[3:11]的中心 PS[7],通过查询长度数组 len,得知以 PS[7] 为中心的最长回文串的长度为 15。显然 PS[3:11] 及 S[1:5] 都是回文的。
偶数长度的字符串同样有效,比如 S[6:7],老铁们可自行验证~
因为 len[mid] 记录的是以 PS[mid] 为中心的最长回文子串的长度。而S[L:R] 对应的PS[L’:R’] 也是以 PS[mid] 为中心的子串。显然:
否则的话就与长度数组 len 的定义矛盾了,所以上述结论一定是成立的。
class Manacher {
public:
Manacher(const std::string &s) {
construct(s);
};
// s[L:R] 是否是回文的
bool isPalindrome(int L, int R) {
L = L*2 + 1;
R = R*2 + 1;
int mid = (L+R)/2;
if(0 <= mid && mid < len.size() && R-L+1 <= len[mid]) {
return true;
}
return false;
}
private:
vector<int> len;
void construct(const std::string &s) {
vector<char> vec;
// 用 0 作为分隔符
vec.resize(s.size()*2+1);
for(int i = 0; i < s.size(); i++) {
vec[i<<1|1] = s[i];
}
int L = 0, R = -1;
len.resize(vec.size());
for(int i = 0, n = vec.size(); i < n; i++) {
if(i <= R) {
len[i] = min((R-i)*2+1, len[L+R-i]);
} else {
len[i] = 1;
}
int l = i - len[i]/2 - 1;
int r = i + len[i]/2 + 1;
while(0 <= l && r < vec.size() && vec[l] == vec[r]) {
--l;
++r;
}
len[i] = r-l-1;
if(r > R) {
L = l+1;
R = r-1;
}
}
}
};