题目
给你一个字符串 s,找到 s 中最长的回文子串。
示例:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
解析
方法一:中心扩散法
当遇到这个问题时我的第一解题思路是滑动窗口法,因为题目中出现了明显的符合滑动窗口的关键词:最长
条件:回文
子串
貌似这是一道很明显的滑动窗口类的算法,但是深入思考后发现,滑动窗口算法并不能实现,原因在于窗口的扩张和收缩条件无法明确。因为在窗口内的字符串没有出现回文串时,是右窗口是向右滑动扩张还是左窗口向右滑动收缩是不确定的,因此这个题目虽然有着滑动窗口的特点,但是无法使用滑动窗口思想解决。
解决这个问题一个比较简单的思路就是中心扩散法。中心扩散法的思想就是从左向右遍历字符串的每一个字符,当遍历到当前字符时就视当前字符为回文串的中心并向左右扩散寻找最大回文子串。在向两边扩散时会出现以下三种可能情况:
1.当前字符和相邻左边字符(left)位置字符是否相同,若相同则left–,回文串长度加一,若不相同则left不变。
2.当前字符和相邻有边字符(right)位置字符是否相同,若相同则right++,回文串长度加一,若不相同则left不变。
3.当前字符的左右两边位置字符是否相同,若相同则left++并且right++,回文串长度加二,若不相同则对比 当前回文串长度是否大于最大回文串长度若大于则更新最大回文串长度,否则中心字符串右移继续扩散寻找
实现代码
string longestPalindrome(string s)
{
int left(0), right(0);
int maxLen(0), len(1), maxstart(0);//给len的赋值应该为1,因为每次检测时默认当前字符为回文串,因此回文串的长度为1
for (int i = 0; i < s.size(); i++)
{
//每次循环时当前字符串为中心位置,左右在其两边
left = i - 1;
right = i + 1;
while (left >= 0 && s[left] == s[i])//判断条件left也可能=0
{
len++;
left--;
}
while (right < s.size() && s[right] == s[i])
{
len++;
right++;
}
while (left >= 0 && right < s.size() && s[left] == s[right])
{
len += 2;//注意若左右两侧字符相同,回文串长度应该+2
left--;
right++;
}
if (maxLen < len)
{
maxLen = len;
maxstart = left + 1;//left指向的是左端回文串前一个字符,因此回文串开始应当是left+1
}
len = 1;//每次循环结束后将len置为1
}
return s.substr(maxstart, maxLen);
}
方法二:动态规划法
动态规划是一种以空间换时间的算法。这个问题用动态规划解决时应注意明确以下几个问题:
1.状态
。使用动态规划思想解决算法问题时往往需要开辟一个数组,这个数组可能是一维数组dp[],也可能是二维数组dp[][]。确定状态就是要确定数组中的每个元素dp[i]或者dp[i][j]代表的是什么。在本题中需要开辟一个二维数组bool类型的dp[i][j]表示字符串s从下标i到下标j的子字符串是否为回文串。
2.状态转移方程
,是依照什么样的规则将原问题转移成为更小的子问题。在本问题中若dp[i][j]是回文串,则dp[i+1][j-1]也应当是回文串也就是说dp[i][j]是否为回文串需要满足两个条件:dp[i+1][j-1]是回文串,并且s[i]==s[j]因此可列出状态转移方程:
dp[i][j] = (dp[][] && s[i]==s[j])
3.临界条件
:在该问题中判断子串是否也是回文串应当也是字符串,即长度大于1,否则就不用判断(单个字符直接返回true)即j-1-(i+1)+1<2时不用判断,j-i<3即j-i+1<4(字符串的长度小于 4)时不用判断子字符串是否为回文
4.初始条件
:单个字符一定是回文串 dp[i][i] = true。例如字符串abcba
的初始时的dp二维数组情况为:
实现代码
//动态规划法
string longestPalindrome(string s)
{
if (s.size() < 2)
{
return s;//若s字符个数小于两个则可直接返回
}
int n = s.length();
int maxLen(1), begin(0);//maxLen置1,因为字符串中最大回文串长度至少是1
vector<vector<int>>dp(n,vector<int>(n));//容器的方法定义二维数组
//C++中if语句内传入一个整型变量或常数只要该变量或常数!=0就执行if语句,不管传入的变量或常数是否为负
//在对dp数组进行赋值操作时应先对列赋值后对行赋值,因为dp[i][j]取值受位于其左下方元素dp[i+1][j-1]的影响,因此在对dp[i][j]赋值时dp[i+1][j-1]的元素应当是已知的
for (int j = 0; j < n; j++)//j表示列
{
dp[j][j] = 1;
for (int i = 0; i < j; i++)//i表示行
{
if (j - i >= 3 && dp[i + 1][j - 1] && s[i] == s[j])
{
dp[i][j] = 1;
}
else if (j - i < 3 && s[i] == s[j])
{
dp[i][j] = 1;
}
else
{
dp[i][j] = 0;
}
if (dp[i][j] && j - i + 1 > maxLen)
{
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substr(begin, maxLen);
}