leetcode 5. 最长回文子串 暴力法、中心扩展算法、动态规划,马拉车算法(Manacher Algorithm)

给定一个字符串 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),感兴趣的同学可以加群讨论、交流、资料查找等,前进的道路上,你不是一个人奥^_^。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值