最长回文子串

动态规划
回文天然具有「状态转移」性质:一个长度严格大于 22 的回文去掉头尾字符以后,剩下的部分依然是回文。反之,如果一个字符串头尾两个字符都不相等,那么这个字符串一定不是回文。「动态规划」的方法根据这样的性质得到。

第 1 步:定义状态
dp[i][j] 表示:子串 s[i..j] 是否为回文子串,这里子串 s[i..j] 定义为左闭右闭区间,即可以取到 s[i] 和 s[j]。

第 2 步:思考状态转移方程
根据头尾字符是否相等,需要分类讨论:


dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
说明:

「动态规划」的「自底向上」求解问题的思路,很多时候是在填写一张二维表格。由于 s[i..j] 表示 s 的一个子串,因此 i 和 j 的关系是 i <= j,只需要填这张表格对角线以上的部分;
看到 dp[i + 1][j - 1] 就需要考虑特殊情况:如果去掉 s[i..j] 头尾两个字符子串 s[i + 1..j - 1] 的长度严格小于 22(不构成区间),即 j - 1 - (i + 1) + 1 < 2j−1−(i+1)+1<2 时,整理得 j - i < 3j−i<3,此时 s[i..j] 是否是回文只取决于 s[i] 与 s[j] 是否相等。结论也比较直观:j - i < 3j−i<3 等价于 j - i + 1 < 4j−i+1<4,即当子串 s[i..j]s[i..j] 的长度等于 22 或者等于 33 的时候,s[i..j] 是否是回文由 s[i] 与 s[j] 是否相等决定。
第 3 步:考虑初始化
单个字符一定是回文串,因此把对角线先初始化为 true,即 dp[i][i] = true。根据第 2 步的说明:当 s[i..j] 的长度为 22 时,只需要判断 s[i] 是否等于 s[j],所以二维表格对角线上的数值不会被参考。所以不设置 dp[i][i] = true 也能得到正确结论。

第 4 步:考虑输出
一旦得到 dp[i][j] = true,就记录子串的「长度」和「起始位置」。没有必要截取,这是因为截取字符串也有性能消耗。

第 5 步:考虑优化空间
下面给出的「参考代码」,在填表的过程中,只参考了左下方的数值。事实上可以优化,但是增加了代码编写和理解的难度,丢失了可读性和可解释性。在这里不做优化空间;
填表应该遵守这样的原则:总是先得到小子串是否是回文的结果,然后大子串才能参考小子串的判断结果,所以填表顺序很重要;
建议自己动手,画一下表格,相信会对「动态规划」作为一种「表格法」有更好的理解。

std::string longestPalindrome(std::string s)
{
	if (s.length() < 2)
	{
		return s;
	}
	std::vector<std::vector<int>> dp(s.length(), std::vector<int>(s.length()));
	for (int i = 0; i < s.length(); i++)
	{
		dp[i][i] = 1;
	}
	int nStart = 0;
	int maxlen = 0;
	for (int j = 1; j < s.length(); j++)
	{
		for (int i = 0; i < j; i++)
		{
			if (s[i] != s[j])
			{
				dp[i][j] = 0;
			}
			else
			{
				if (j - i < 3)
				{
					dp[i][j] = true;

				}
				else
				{
					dp[i][j] = dp[i+1][j - 1];
				}
			}
			if (dp[i][j] && j - i + 1 > maxlen)
			{
				maxlen = j - i + 1;
				nStart = i;
			}
		}
		
	}
	return s.substr(nStart, maxlen);
}


链接:https://leetcode.cn/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值