给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案
首先,看懂题目很重要!!!啥是回文?
把相同的词汇或句子 [1] ,在下文中调换位置或颠倒过来,产生首尾回环的情趣,叫做回文,也叫回环。
这道题对时间复杂度没有要求,可即便如此,刚开始还是毫无办法 ╮(╯-╰)╭。想了一会儿,遍历吧,俗称暴力法,毕竟解决问题最要紧,优化可以继续搞。通过简单分析可以知道,回文的字符串两两对应相等。假设长度为n的回文字符串,满足如下条件
s[i] == s[n-i-1]
我们遍历给定字符串的每一个字符,找到以它开始的最长回文字符串。对于给定的起始字符,考虑最长,我们肯定是从字符串末尾从后往前遍历,使用first, last两个指针分别指向首尾,遍历直到first > last则结束遍历,这时候保留得到的长度和起始位置。开始下一个字符遍历。
随着遍历的过程中,我们可以发现上述的暴力法是可以优化的,举个例子,s[ i ] 的最长回文子串尾指针为s[ j ],当我们遍历i下一个元素 s[ i + 1]的时候,我们通过上一步可以直到s[ i + 1] 和 s[ j - 1 ]是回文的,所以只需要遍历 j+1 ~ last 这段区间即可。同理,对于后续的每个元素我们都可以这样做,只需要从末尾元素遍历到先前最近的回文字符即可。假设对于每个起始字符 first[ i ],它对应的最长回文子串尾字符为 last[ i ], 当我们继续遍历首字符 first[j](i < j < n)时,只需要遍历MAX(last[0], last[1]...last[j -1])到n的区间即可。这样可以节省一点不必要的操作。思想和滑动窗口解决无重复最长子串问题一样。
char * longestPalindrome(char * s){
int first, last, len = 0, i, j, max = 0, cursor, jump = 0;
char *ps = s;
if (!s)
return s;
while(*ps++ != '\0')len++;
//遍历每个字符
for (i = 0; i < len; i++)
{
for (j = len - 1; j >= jump; j--)
{
first = i, last = j;
while (first <= last){
if (s[first++] != s[last--])
break;
}
if (s[first-1] == s[last+1]){
if (j - i + 1 > max)
{
max = j - i + 1;
cursor = i;
}
//jump表示可以省略的位置,i后面的元素不用再与jump左边的元素比较
//和滑动窗口思想一样。
jump = jump > last + 1 ? jump: (last + 1);
break;
}
}
}
ps = s + cursor;
if (max < len)
ps[max] = '\0';
return ps;
}
暴力遍历法时间复杂度为O(n^3),中心扩展方法时间复杂度为O(n^2),这个方法其实是相当简单的。如果说暴力遍历回文串方向是从两端匹配到中间的话,中心扩展方向是从中心到两端。这里中心有2N-1个,包括奇数中心N和偶数中心N-1。中心扩展方法即从中心向两端遍历,遇到不匹配则退出。
#define MAX(a,b) ((a)>(b)?(a):(b))
int centerExpand(char *s, int len, int left, int right)
{
while(left >= 0 && right < len && s[left] == s[right])
{
left--;
right++;
}
return right - left - 1;
}
char * longestPalindrome(char * s){
int len = 0, center = 0, max = 0, max0 = 0, max1 = 0, cursor = 0;
char *pStr = s;
if (!s)return s;
while (*pStr++ != '\0')len++;
for (center = 0; center < len; center++)
{
max0 = centerExpand (s, len, center, center);
max1 = centerExpand (s, len, center, center+1);
if (max < max0 || max < max1)
{
if (max0 > max1)
cursor = center - max0 / 2;
else
cursor = center + 1 - max1 /2;
max = MAX(max0, max1);
}
}
pStr = s + cursor;
if (max != len)
pStr[max] = '\0';
return pStr;
}
第三种是动态规划,这个等我学会再写出来。
=========================================================================================
动态规划的基本步骤可分为:
1. 分阶段
2. 状态递推方程
3. 求最优解。
这道题如果使用动态规划,显然要使用m[i][j]类型,假设i和j分别表示字符的位置,m[i][j]表示字符i到j是否为回文字符串。如果是的话m[i][j] = 1; 否则 m[i][j] = 0; 递推方程为m[i][j] = m[i+1][j-1] && (s[i] == s[j]) ; 我们可以先算出长度为1和2的m[][]值,
m[i][i] = 1; m[i][i+1] = (s[i] == s[i+1]);接着递推,在递推过程中,可以记录最长回文字符串值max = j - i + 1;起点为i.
难度不大,就是不容易想到,平常使用动态规划的时候m[i][j]用来表示最优解,本题只保存了状态。需要多多练习。
下面是动态规划的c代码:
char * longestPalindrome(char * s){
int i,j,d, len = 0, m[1001][1001]={0}, max = 1, c = 0;
char *pStr = s;
if (!s)return s;
while (*pStr++ != '\0')len++;
if (len <= 1)
return s;
//初始化边界,单字母回文和双字母回文
for (i = 0; i < len - 1; i++)
{
m[i][i] = 1;
if (s[i] == s[i+1])
{
m[i][i+1] = 1;
if (max < 2)
{
max = 2;
c = i;
}
}
else
m[i][i+1] = 0;
}
m[i][i] = 1;
//递推,查询3字母到n-1字母长度
for (d = 2; d < len; d++)
for (i = 0; i + d < len; i++)
{
j = d + i;
m[i][j] = (m[i+1][j-1]) && (s[i] == s[j]);
if (m[i][j] && (j-i+1) > max)
{
max = j - i + 1; c = i;
}
}
s = s + c;
s[max] = '\0';
return s;
}
=============================================================================================
Linux应用程序、内核、驱动、后台开发交流讨论群(745510310),感兴趣的同学可以加群讨论、交流、资料查找等,前进的道路上,你不是一个人奥^_^。