大家好,今天为大家分享一个算法题类型——回文子串问题
“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。
字串指的是在字符串中任意个连续的字符组成的子序列。
一、回文子串
我们采用动态规划的解法
因此就需要我们提前准备一个n*n的dp表,每个位置dp[j][i]表示,以j位置起始,i位置结束的字串是否为回文串。
分析状态转移方程:
1、当s[j]!=s[i]时,说明以j位置起始,i位置结束的字串不可能为回文串
2、当s[j]==s[i]时,可根据该字串的长度来分析
①该字串长度为1时,一定为回文串,dp[j][i]=true
②长度为2时,即j+1等于i,此时也一定为回文串,dp[j][i]=true
③长度大于2时,就要看以j+1位置起始,i-1位置结束的字串是否为回文串了,即dp[j][i]=dp[j+1][i-1];
然后我们就可以根据dp表中true值的个数来统计回文子串的个数。因此,我们在创建dp表格时可以将所有的值初始化为false.
代码如下:
class Solution {
public:
int countSubstrings(string s) {
int n=s.size();
int ret=0;
vector<vector<bool>>dp(n,vector<bool>(n));
//填dp表时要确保填当前位置时,他前面的位置已经填过了
for(int i=0;i<n;i++)
{
for(int j=0;j<=i;j++)
{
if(s[j]==s[i])
{
dp[j][i]=j+1<i?dp[j+1][i-1]:true;
}
if(dp[j][i])
ret++;
}
}
return ret;
}
};
二、最长回文字串
我们采用动态规划的解法:
上题我们已经能够判断每个字串是否是回文子串,因此,我们可以在此基础上,在判断完每个字串是回文串后记录一下与他的长度,如果当前长度大于已经记录的最大长度时,更新最长字串做右端点和最长字串的长度,最终就可以得到最长字串!!!
代码如下:
class Solution {
public:
string longestPalindrome(string s) {
int n=s.size();
vector<vector<bool>>dp(n,vector<bool>(n));
int len=0;
int left=0,right=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<=i;j++)
{
if(s[i]==s[j])
{
dp[j][i]=j+1<i?dp[j+1][i-1]:true;
}
if(dp[j][i])
{
//当前字串长度大于已经记录的最大长度,则更新
if((i-j+1)>len)
{
len=i-j+1;
left=j;
right=i;
}
}
}
}
return string(s.begin()+left,s.begin()+right+1);
}
};
三、分割回文串Ⅳ
啊哈,这还是一道困难类型的题!!!
解体思路:我们依旧采用动态规划,老样子,我们前面写过判断一个字符串的字串中有多少回文字串,此题依旧需要用到那道题的思路。当我们用一个dp表描述出每个字串是否是回文串后,我们可以依次枚举该字符串的被分割为三个字串后的第二个字串的起始位置和结束位置,然后就可以得到第一个字串和第三个字串的起始结束位置,然后判断这三个字串是否为回文串(dp表中已经给出了),如果其中有一个分割方式能够使三个字串全部为回文串,那就返回true,一个都不存在的话就返回false…
代码如下:
class Solution {
public:
bool checkPartitioning(string s) {
int n=s.size();
vector<vector<bool>>dp(n,vector<bool>(n,false));
for(int i=n-1;i>=0;i--)
{
for(int j=i;j<n;j++)
{
if(s[i]==s[j])
{
dp[i][j]=i+1<j?dp[i+1][j-1]:true;
}
}
}
// 2. 枚举所有的第⼆个字符串的起始位置以及结束位置
for(int i=1;i<n-1;i++)
{
for(int j=i;j<n-1;j++)
{
if(dp[0][i-1]==true&&dp[i][j]==true&&dp[j+1][n-1]==true)
return true;
}
}
return false;
}
};