leetcode 647.回文子串
题目描述
思路
这是一个比较巧妙的方法,实质的思路和动态规划的思路类似。
比如对一个字符串 ababa
,选择最中间的 a
作为中心点,往两边扩散,第一次扩散发现 left
指向的是 b
,right
指向的也是 b
,所以是回文串,继续扩散,同理 ababa
也是回文串。
这个是确定了一个中心点后的寻找的路径,然后我们只要寻找到所有的中心点,问题就解决了。
中心点一共有多少个呢?看起来像是和字符串长度相等,但你会发现,如果是这样,上面的例子永远也搜不到 abab
,想象一下单个字符的哪个中心点扩展可以得到这个子串?似乎不可能。所以中心点不能只有单个字符构成,还要包括两个字符,比如上面这个子串 abab
,就可以有中心点 ba
扩展一次得到,所以最终的中心点由 2 * len - 1
个,分别是 len
个单字符和 len - 1
个双字符。
如果上面看不太懂的话,还可以看看下面几个问题:
- 为什么有
2 * len - 1
个中心点?
aba
有5个中心点,分别是a、b、c、ab、ba
abba
有7个中心点,分别是a、b、b、a、ab、bb、ba
- 什么是中心点?
中心点即left
指针和right
指针初始化指向的地方,可能是一个也可能是两个 - 为什么不可能是三个或者更多?
因为3
个可以由1
个扩展一次得到,4
个可以由两个扩展一次得到 - 如何有序地枚举所有可能的回文中心?
我们需要考虑回文长度是奇数和回文长度是偶数的两种情况。
如果回文长度是奇数,那么回文中心是一个字符;如果回文长度是偶数,那么中心是两个字符。
当然你可以做两次循环来分别枚举奇数长度和偶数长度的回文,但是我们也可以用一个循环搞定。我们不妨写一组出来观察观察,假设 n = 4 n=4 n=4,我们可以把可能的回文中心列出来:
由此我们可以看出长度为 n n n的字符串会生成 2 n − 1 2n−1 2n−1 组回文中心 [ l i , r i ] [l_{i},r_{i}] [li,ri],其中 l i = ⌊ l_{i}=\lfloor li=⌊ i 2 \frac{i}{2} 2i ⌋ \rfloor ⌋, r i = l i + ( i % 2 ) r_{i}=l_{i}+(i \%2) ri=li+(i%2)。这样我们只要从 0 0 0 到 2 n − 2 2n−2 2n−2 遍历 i i i,就可以得到所有可能的回文中心,这样就把奇数长度和偶数长度两种情况统一起来了。
代码
class Solution {
public:
int countSubstrings(string s) {
int res=0;
for(int i=0;i<2*s.length()-1;i++)
{
int left=i/2,right=left+i%2;
while(left>=0&&right<s.length()&&s[left]==s[right])
{
res++;
left--;
right++;
}
}
return res;
}
};
leeccode 5.最长回文子串
题目描述
思路
在前面求一个字符串的所有回文子串的基础上,改良一下
先求出最大的回文子串长度,再在求回文子串的过程中,求出最长长度的回文子串。
代码
class Solution {
public:
string longestPalindrome(string s) {
int ml=0;
int left,right,sum;
for(int i=0;i<2*s.length()-1;i++)
{
left=i/2,right=left+i%2;
if(i%2)
sum=0;
else
{
sum=1;
left--;
right++;
}
while(left>=0&&right<s.length()&&s[left]==s[right])
{
sum+=2;
left--;
right++;
}
ml=max(ml,sum); //求出了最大长度
}
int r,l; //保留有效的回文子串边界
for(int i=0;i<2*s.length()-1;i++)
{
left=i/2,right=left+i%2;
if(left==right)
{
sum=1;
left--;
right++;
}
else
sum=0;
while(left>=0&&right<s.length()&&s[left]==s[right])
{
sum+=2;
l=left,r=right;
left--;
right++;
}
if(sum==ml)
{
string res;
for(int j=l;j<=r;j++)
res+=s[j];
return res;
}
}
string n;
return n;
}
};