1、题目
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
链接:https://leetcode-cn.com/problems/palindromic-substrings/
2、思路分析
1)暴力求解法
中心扩展法:字符串从左到右的每个字符,都进行一次中心扩展,以该字符为中心,向两边扩展,判断是不是回文子串,如果是则 ans++,不是则遍历下一个字符,直至字符串全部遍历完毕。
class Solution {
public:
int countSubstrings(string s) {
// 中心扩展法
int n = s.size();
int ans = 0;
for(int i = 0; i < 2*n-1; ++i)
{
int l = i / 2, r = i /2 + i%2;
while(l >= 0 && r <= n && s[l] == s[r])
{
l--,r++;
ans++;
}
}
return ans;
}
};
2)manacher算法
在遍历偶数字符串的时候,会出现中心扩展的错误结果,所以将字符串的个数转为为奇数的方法,字符串的间隔中加 ‘#’,这样该字符串就会变成奇数个数。
遍历新的字符串,将每个字符的的最大扩展半径计算出来,放在一个数组中,首先先给这个数组初始化,之后依旧是中心扩展算出最大的半径,然后更新中心字符和扩展的边界R,最终计算回文子串的个数。
class Solution {
public:
int countSubstrings(string s) {
// manacher算法
int n = s.size();
// mach存储加了#的字符串,maxR保存以每个下标为中心的最长匹配半径
string mach = "$#";
for(const char& c : s)
{
mach += c;
mach += '#';
}
//cout << mach<< endl;
n = mach.size();
mach += '!';
int R = 0, C = 0;
int ans = 0;
auto maxR = vector<int> (n); // 注意n;因为在这个代码中,n是变化的,不注意会导致越界行为
for(int i = 1; i < n; ++i)
{
// 第一步计算最短的回文半径
maxR[i] = (i <= R) ? min(R - i + 1, maxR[2*C - i]) : 1;
// 暴力求解
while(mach[i + maxR[i]] == mach[i - maxR[i]])
{
++maxR[i];
}
// 更新R,C
if(i + maxR[i] - 1 > R)
{
R = i + maxR[i] - 1;
C = i;
}
ans += (maxR[i] / 2);
}
return ans;
}
};