leetcode--5. 最长回文子串(dp/字符串hash/manacher)
记录
23:29 2024-2-5
https://leetcode.cn/problems/longest-palindromic-substring/
1. dp方法
dp[i][j] s[i, j] 之间能否构成回文子串
[i,j]之间是否能够构成需要考虑[i+1, j-1]是否构成回文子串且s[i] == s[j]
当j-1 >= i+1 时候说明正好是 俩个相邻的字符,此时如果s[i] == s[j]就肯定可以构成回文子串
\[dp[i][j] = \begin{cases}
dp[i + 1][j - 1] \qquad if \ \ j - 1 >= i + 1 \&\ \& s[i] == s[j] \\
1 \qquad \qquad \qquad \qquad else \ \ if \ \ s[i] == s[j]
\end{cases}
\]
点击查看代码
class Solution {
public:
// dp[i][j] [i, j] 之间能否构成回文子串
/* dp[i][j] = {
dp[i + 1][j - 1] if j - 1 >= i + 1 && s[i] == s[j]
1 else s[i] == s[j]
}*/
string longestPalindrome(string s) {
int dp[s.size()][s.size()];
memset(dp, 0, sizeof(dp));
int begin = 0, maxSize = 1;
for(int i = s.size() - 1; i >= 0; i--) dp[i][i] |= 1;
for(int i = s.size() - 1; i >= 0; i--) {
for(int j = i + 1; j < s.size(); j++) {
if(s[i] == s[j]) {
if(j-1 >= i+1) {
dp[i][j] |= dp[i+1][j-1];
} else {
dp[i][j] |= 1;
}
//注意需要判断dp[i][j]
if(dp[i][j] && maxSize < j - i + 1) {
maxSize = j - i + 1;
begin = i;
}
}
}
}
return s.substr(begin, maxSize);
}
};
2.字符串hash
字符串hash
hash函数是一个可以将输入映射为值的函数(我自己理解的,更加学术的定义我清楚),这个hash函数最好可以让不同值得到不同的结果,如果有冲突的话可以用开放地址放、散列法。也可以使用多个hash进行区分(详情如数据库原理中的cuckoo hashing--布谷hash)
字符串hash就是将字符串转化为一个值,这里的方法是将字符串看成P进制数,P取值为131/13331,因为此时产生冲突的概率低(细节证明我猜需要数论知识,哈哈没学过)
然后就可以遍历字符串s中的每个位置i:
- 奇回文串 s[i - midp ~ i] = reverse(s[i ~ i + midp])
- 偶回文串 s[i - midq ~ i - 1] = reverse(s[i ~ i + midq - 1]) (使用了不同midp、midq分别用来二分)
前缀hash值很容易计算
注意对于reverse的计算,可以维护一个类似于前缀hash值的,不过开始是从末尾开始:
- 对于prefixHash f[r] - f[l-1] * p[r-l+1] (prefixHash就是上面 s[i - midp ~ i] = reverse(s[i ~ i + midp]) 中左边等式)
- 对于reverseHash f[l] - f[r+1] * p[r-l+1]
点击查看代码
class Solution {
public:
// 字符串hash
string longestPalindrome(string s) {
// 为了方便处理,加一个空格
s = ' ' + s;
unsigned long long prefixHash[s.size() + 1], revserHash[s.size() + 1], p[s.size() + 1];
memset(prefixHash, 0, sizeof(prefixHash));
memset(revserHash, 0, sizeof(revserHash));
memset(p, 0, sizeof(p));
p[0] = 1;
for(int i = 1; i < s.size(); i++) {
// [1 ~ i]
prefixHash[i] = prefixHash[i - 1] * 131 + s[i] - 'a' + 1;
p[i] = p[i - 1] * 131;
}
for(int i = s.size() - 1; i >= 1; i--) {
// [s.size() - i ~ i] 逆向计算
revserHash[i] = revserHash[i + 1] * 131 + s[i] - 'a' + 1;
}
// 枚举可能的中心位置
int begin = 1, maxSize = 1;
for(int i = 1; i < s.size(); i++) {
int lp = 1, rp = min(i, (int)s.size() - i);
int lq = 1, rq = min(i, (int)s.size() - i);
// 二分法寻找
while(true) {
if(lp > rp) break;
int midp = (lp + rp) / 2;
//if(2 * mid < maxSize) break;
if(i - midp - 1 < 0 || i + midp + 1 > s.size()) {
rp = midp - 1;
continue;
}
// s[i - midp ~ i] = reverse(s[i ~ i + midp])
// 对于prefixHash f[r] - f[l-1] * p[r-l+1]
// 对于reverseHash f[l] - f[r+1] * p[r-l+1]
if(prefixHash[i] - prefixHash[i - midp - 1] * p[midp + 1] ==
revserHash[i] - revserHash[i + midp + 1] * p[midp + 1]) {
lp = midp + 1;
if(2 * midp + 1 > maxSize) {
maxSize = 2 * midp + 1;
begin = i - midp;
}
} else {
rp = midp - 1;
}
}
while(true) {
if(lq > rq) break;
int midq = (lq + rq) / 2;
if(i - midq - 1 < 0 || i + midq > s.size()) {
rq = midq - 1;
continue;
}
// s[i - midq ~ i - 1] = reverse(s[i ~ i + midq - 1])
if(prefixHash[i - 1] - prefixHash[i - midq - 1] * p[midq] ==
revserHash[i] - revserHash[i + midq] * p[midq]) {
lq = midq + 1;
if(2 * midq > maxSize) {
maxSize = 2 * midq;
begin = i - midq;
}
} else {
rq = midq - 1;
}
}
}
return s.substr(begin, maxSize);
}
};
3.manacher
点击查看代码
class Solution {
public:
// manacher
string longestPalindrome(string s) {
int n = s.size();
vector<int> d1(n);
for (int i = 0, l = 0, r = -1; i < n; i++) {
int k = (i > r) ? 1 : min(d1[l + r - i], r - i + 1);
while (0 <= i - k && i + k < n && s[i - k] == s[i + k]) {
k++;
}
d1[i] = k--;
if (i + k > r) {
l = i - k;
r = i + k;
}
}
vector<int> d2(n);
for (int i = 0, l = 0, r = -1; i < n; i++) {
int k = (i > r) ? 0 : min(d2[l + r - i + 1], r - i + 1);
while (0 <= i - k - 1 && i + k < n && s[i - k - 1] == s[i + k]) {
k++;
}
d2[i] = k--;
if (i + k > r) {
l = i - k - 1;
r = i + k;
}
}
int begin = 0, maxSize = 1;
for(int i = 0; i < n; i++) {
if(2 * d1[i] - 1 > maxSize) {
maxSize = 2 * d1[i] - 1;
begin = i - d1[i] + 1;
}
if(2 * d2[i] > maxSize) {
maxSize = 2 * d2[i];
begin = i - d2[i];
}
}
return s.substr(begin, maxSize);
}
};