5. 最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:

输入: "cbbd"
输出: "bb"

 

别人想法:

(1)

一个字符的串肯定是一个回文串;
两个字符只要相同,它们在一起也构成一个回文串;
三个字符的串,只要首尾两个字符相同,那它也是一个回文串;
以此类推,如果一个字符串,处于它的首尾两个字符之间的子串是回文串,并且它的首尾字符相同,则它就是一个回文串。
总结,当一个串有三个字符时,我们可以用中间一个字符的情况加上它的首尾字符是否相同推出来;当一个串有四个字符时,我们可以用中间两个字符的情况加上它的首尾字符是否相同推出来,按照这个规律递推下去,就能判断所有长度的子串(包括相同长度的不同子串)是否是回文串。
因此,我们需要一个二维数组 bool dp[len][len] 来缓存一个串是否是回文串的结果,第一维是字符串的首字符的下标,第二维是字符串的尾字符的下标,用 dp[i][j] 的值表示str[i…j]这个串是否是回文串,true代表是,false代表否。这样就可以缓存每个串是否是回文串的结果,当我们枚举所有串来判断它们是否是回文串时,就可以借助缓存的结果(当前串去掉首尾字符的子串是不是回文串),用O(1)的时间复杂度快速地对当前串做出判断。
我们定义:

dp[i][j] = true(当串str[i…j]是回文串)
dp[i][j] = false(当串str[i…j]不是回文串)

并且可以得到以下的状态转换方程:

dp[i][j] = (dp[i + 1][j - 1] && str[i] == str[j])

具体解法如下:
当串长度为以 1 时,dp[i, i] = true(0 <= i < len);
当串长度为 2 时,dp[i, i+1] = (str[i] == str[i+1]) (0 <= i < len - 1),前两种情况很简单,我们直接判断并初始化dp数组;
当串长度大于等于3时,我们使用状态转换方程进行递推。

动态规划的过程如下图:

è¿éåå¾çæè¿°

(2)

假设任意给你一个串,例如:aasbavvaba那么串长len可以划分:1)len = 1; 2)len = 2; 3)len > 2;这样划分的理由是:任何串内最短的回文串长len>=1,这点毋庸置疑,而划分长度为2是因为,但凡一个子串长度len大于2都是在长度为1或2的基础上增加的。判断规则:每次枚举一个小于等于len的长度i然后判断下标j与下标j+i所对应的字符是否相等,不等怎j向后枚举,若想等,此时需要判断从j+1到j+i-1是否为回文串,是则更新不是则j向后继续枚举。这样就建立起一个关系,也就是当前状态只和上一个状态有关,若上一个状态是回文串(这里代码中用dp数组保存状态为1表示上一个状态为回文串,否则不是),此时只需判断s[i]与s[j+i]是否相等即可。

[注意]:dp[i][j]表示从下标i到j为会问串,因此i<=j,这里就要避免i>j的情况,因为若枚举两个相邻的字符时,那么dp[j+1][j+i-1]中j+1 > j+i-1,因此我们自己手动将dp[j+1][j]初始化为1,表示这种情况是正常的即可。
 

自己想法:

首先想到动态规划定义,将这个大问题分解成一个个的小问题的解,而这些小问题的解之间还有联系(先求出1/2子串,根据这两个子串再向上查找最长子串),最后将小问题的解汇总出最终解。

这道题精髓就在设计那个二维数组,横坐标长度是s.size(),纵坐标长度也是s.size()。横纵坐标可以想象是两个游标,横坐标是第一个游标(判断最长子串的起点),纵坐标是第二个游标(判断最长子串的终点),之后组成一个二维矩阵,之后根据这个矩阵和首尾字符串是否相等,来判断是否更新最长字符串。具体解析在代码注释。

#include<vector>
class Solution {
public:
    string longestPalindrome(string s) {
       int s_length = s.size();
        if (s_length ==0 || s_length ==1)
        {
            return s;
        }
        int max = 1;
        int start = 0;
        vector<vector<int>>bp(s_length,vector<int>(s_length));//需要复习二维数组初始化
        //下面这个for循环是初始化bp数组,也是找出基础的1、2最长子串,为下面的for循环做准备,因为以后长度的子串都是以1、2长度子串做基础
        for (int i = 0; i < s_length;i++)
        {
            bp[i][i] = 1;//这个一开始错了,应该是1,这个是当长度为1时,二维矩阵对角线为1;
            if (i < s_length-1 && s[i] == s[i+1])
            {
                bp[i][i+1] = 1;
                max = 2;
                start = i;
            }
        }
        //下面这个循环,第一个for循环是最长子串长度递增,第一个是长度为3的子串,第二个for循环就是游标递增,
        //长度为3时,则首游标就是先从0开始,尾游标就是2(3+0-1),然后首游标k递增(1、2、3...)往后寻找长度为3子串
        //下面的if循环就是判断,因为上面初始化时有最小2个最长子串,所以判断长度为3子串时候时,只需要
       //长度为2的子串bp数组 == 1 和首尾字符串相等,就可以推出长度为3的字符串,这也就是动态规划的含义所在,
        //求更长子串,需要短的子串做基础。
        //而start和max的意义就是最后知道从哪裁剪字符串
        for (int l = 3; l <= s_length; l++)//这块错了,这应该是<=,因为l是最长长度,是可以等于s.length()的
        {
            for (int k = 0; l + k - 1< s_length; k++)//这块l + k - 1< s_length判别条件自己一开始写错了。注意应该是后面的游标<s_length
            {
                int m = l + k - 1;
                if (bp[k+1][m-1] == 1 && s[k] == s[m])
                {
                    max = l;
                    start = k;
                    bp[k][m] = 1;
                }
            }
        }
        return s.substr(start, max);
    }
};

还有一种想法:中心扩算法,时间不够没看代码,把思想消化一下,有时间再看(官网解答)

https://leetcode-cn.com/problems/longest-palindromic-substring/solution/5-zui-chang-hui-wen-zi-chuan-cc-by-bian-bian-xiong/

我们观察到回文中心的两侧互为镜像。因此,回文可以从它的中心展开,并且只有 2n - 1 个这样的中心。

你可能会问,为什么会是 2n - 1 个,而不是 n 个中心?

因为回文的中心要区分单双。

假如回文的中心为 双数,例如 abba,那么可以划分为 ab bb ba,对于n长度的字符串,这样的划分有 n-1 种。

假为回文的中心为 单数,例如 abcd, 那么可以划分为 a b c d, 对于n长度的字符串,这样的划分有 n 种。

对于 n 长度的字符串,我们其实不知道它的回文串中心倒底是单数还是双数,所以我们要对这两种情况都做遍历,也就是 n+(n-1)= 2n - 1,所以时间复杂度为 O(n)。

当中心确定后,我们要围绕这个中心来扩展回文,那么最长的回文可能是整个字符串,所以时间复杂度为 O(n)。

所以总时间复杂度为 O(n^2)

	string longestPalindrome(string s) 
	{
		if (s.length() < 1)
		{
			return "";
		}
		int start = 0, end = 0;
		for (int i = 0; i < s.length(); i++)
		{
			int len1 = expandAroundCenter(s, i, i);//一个元素为中心
			int len2 = expandAroundCenter(s, i, i + 1);//两个元素为中心
			int len = max(len1, len2);
			if (len > end - start)
			{
				start = i - (len - 1) / 2;
				end = i + len / 2;
			}
		}
		return s.substr(start, end - start + 1);
	}

	int expandAroundCenter(string s, int left, int right)
	{
		int L = left, R = right;
		while (L >= 0 && R < s.length() && s[L] == s[R])
		{// 计算以left和right为中心的回文串长度
			L--;
			R++;
		}
		return R - L - 1;
	}

参考文章:

https://blog.csdn.net/CSDN_FengXingwei/article/details/82429808

https://blog.csdn.net/small__snail__5/article/details/80310615

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值