给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
输入: “cbbd”
输出: “bb”
求最长、最大值这种问题,一般可能会用到动态规划,所以需要寻找之间的规律,在该题中如果位置 i 是回文串,那与i - 1
位置有什么联系呢?
假定已经存储好0 ~ (i - 1)
每个位置的最长回文子串长度(以自己向前的回文子串,包括自己),对于 i 位置的字符,如果i - 1
位置回文串前边那个字符与 i 位置字符相同s[i - 1 - dp[i - 1]] == s[i]
,则可以延长回文串,即dp[i] = dp[i - 1] + 2
,如果不等,则需要从i - dp[i - 1]
开始向 i 遍历,寻找回文串,找到则存入 dp[i] ,跳出该层循环。另外在遍历 i 的同时,记录最大长度和有最大长度的位置。运行时间4ms,代码如下。
bool isPalind(char* s, int len) {
int i = 0, j = len / 2;
for(i = 0; i < j; i++) {
if(s[i] != s[len - 1 - i])
return false;
}
return true;
}
char* longestPalindrome(char* s) {
int len = strlen(s);
int* dp = (int*)malloc(len * sizeof(int));
dp[0] = 1;
int i = 0, max = 1, index = 0, j = 0;
for(i = 1; i < len; i++) {
if((i - 1 - dp[i - 1] >= 0) && s[i] == s[i - 1 - dp[i - 1]])
dp[i] = dp[i - 1] + 2;
else {
for(j = i - dp[i - 1]; j <= i; j++) {
if(isPalind(s + j, i - j + 1)) {
dp[i] = i - j + 1;
break;
}
}
}
if(dp[i] > max) {
max = dp[i];
index = i - dp[i] + 1;
}
}
s[index + max] = '\0';
free(dp);
return s + index;
}
上述解法中对于s[i - 1 - dp[i - 1]] != s[i]
的情况下,需要重新从i - dp[i - 1]
开始遍历,实际上这个位置时间复杂度还挺高。所以换一种动态规划的思路。
假设我们存储一个二维数组,dp[i][j]
表示从字符串 s 的 i 位置到 j 位置,其是否为一个回文串,是则标记为true,不是则标记为false。易知对角线上必定为true,对于dp[i][j]
,如果s[i] == s[j] && dp[i + 1][ j - 1]
,则dp[i][j] = true
,否则为false;因为最终结果是对称的,所以只选用右上三角或左下三角即可,为了更加直观理解,此处选用正方形的右上三角。因为在访问dp[i][j]
时,dp[i + 1][ j - 1]
应该已经被标记过,所以会涉及到一个遍历顺序的问题,应该时从下向上遍历行,从左向右遍历列。注意两个相同的字符相邻时,其也是回文串。运行时间32ms,代码如下。
char* longestPalindrome(char* s) {
int len = strlen(s);
bool** dp = (bool**)malloc(len * sizeof(bool*));
dp[0] = 1;
int i = 0, max = 1, index = 0, j = 0;
for(i = 0; i < len; i++) {
dp[i] = (bool*)calloc(len, sizeof(bool));
dp[i][i] = true;
}
for(i = len - 1; i >= 0; i--) {
for(j = i + 1; j < len; j++) {
if(s[i] == s[j] && (dp[i + 1][j - 1] || i + 1 == j)) {
dp[i][j] = true;
if(j - i + 1 > max) {
max = j - i + 1;
index = i;
}
}
}
}
s[index + max] = '\0';
for(i = 0; i < len; i++)
free(dp[i]);
free(dp);
return s + index;
}
奇数长度回文串中,对于某字符,向左右两侧同时扩散,如果左右字符相同,继续向外扩散,直到两侧字符不同,返回长度;偶数长度时,首先需要相邻的两个字符相同,然后再同时向左右扩散,直到不同返回长度。所以遍历 i ,i 向两边扩散,找到其最长回文串,返回长度,比较奇数长度和偶数长度,记录最大值,并记录起始索引。运行时间4ms,代码如下。
int getPalind(char* s, int left, int right, int n) {
int i = 0;
while(left >= 0 && right < n) {
if(s[left] != s[right])
break;
else {
left--;
right++;
}
}
return right - left - 1;
}
char* longestPalindrome(char* s) {
int len = strlen(s);
int i = 0, max = 0, index = 0;
for(i = 0; i < len; i++) {
int odd = getPalind(s, i, i, len);
int even = getPalind(s, i, i + 1, len);
if(odd > max) {
max = odd;
index = i - (odd >> 1);
}
if(even > max) {
max = even;
index = i + 1 - (even >> 1);
}
}
s[index + max] = '\0';
return s + index;
}