最大回文子串(中心点扩展与Manacher算法)

问题描述

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

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

输入: “cbbd”
输出: “bb”

题目来源:5. 最长回文子串 - 力扣(LeetCode)

中心点扩展算法

思想

  1. 选择一个中心点,向两边扩展,直至不符合回文串要求;
  2. 时间复杂度:O(n2);空间复杂度:O(1);

源码

//中心点扩展
//时间复杂度:O(n)
//空间复杂度:O(1)
string longestPalindrome(const string& str) {
	if (str.length() < 2) return str;
	int start = 0, len = 0;
	int slen = 2 * str.length() - 1;
	for (int i = 0; i < slen; ++i) {
		int left = i / 2;
		int right = (i + 1) / 2;
		//向外扩展,直至不符合回文串要求
		while (left >= 0 && right < str.length() && str[left] == str[right]) {
			--left;
			++right;
		}
		++left;
		if (len < (right - left)) {
			len = right - left;
			start = left;
		}
	}
	return str.substr(start, len);
}

Manacher算法

思想

  1. Manacher算法是在O(n)时间内解决寻找源字符串的最长回文子串S的问题的算法。
  2. Manacher将偶回文串和奇回文串统一转换为奇回文串的计算;并利用对称的思想,当已知字符串前i个字符中的中心回文串的最大右边界时,可以先对当前中心点i的回文串的半径进行初步更新,以减少扩展次数;
    if (i < c + dp[c]) dp[i] = min(dp[2 * c - i], dp[c] + c - i);
    其中,c为已知的最大右边界对应的回文串的中心点;

实现步骤

  1. 首先,为方便计算偶回文串和奇回文串的情况,将字符串转换为以‘#’间隔的字符串,并在首尾分别插入’$'以及‘^’以自动结束扩展,而不必判断终止条件;
    例如:字符串“abcd”转换为“$#a#b#c#d#^”;即原字符串中第i个位置处于新字符串的第2*i+2个位置;
    我们也可以知道在新字符串中,回文串必然是以#开始,以#结束的,且#必然在奇数位;如:[#a…a#],设left=res-dp[rc]+1和right=rc+dp[rc]-1分别为#开始和结束的坐标;那么转换为原数据str上的索引为start=left/2,end=right/2;
  2. 创建dp数组,dp[i]表示为i为中心的最大回文串的半径;
  3. 记录当前最大右边界对应回文串的中心点c;
  4. 算法核心:如果当前中心点处于已知回文串最大右边界内,则直接采用以下公式更新dp[i]:
    dp[i] = min(dp[2 * c - i], dp[c] + c - i);
  5. 此时,dp[i]表示当前以i为中心点的回文串的半径;并在此基础上进一步向外扩展;
  6. 更新当前最大右边界对应回文串的中心点c以及最大回文子串的中心点;

参考:
https://segmentfault.com/a/1190000008484167?utm_source=tag-newest
https://blog.csdn.net/mlm5678/article/details/89820921

源码

//Manacher
//时间复杂度:O(n)
//空间复杂度:O(n)
string longestPalindrome(const string& str) {
	if (str.length() < 2) return str;
	//构建Manacher数据
	string s(2 * str.length() + 3, '#');
	s[0] = '$'; s.back() = '^'; //方便自动退出
	for (int i = 0; i < str.length(); ++i)
		s[2 * i + 2] = str[i];
	vector<int>dp(s.length(), 1);
	dp[0] = -1;
	//当前最大右边界对应回文串的中心点
	int c = 0;
	//最大回文子串的中心点
	int rc = 0;
	for (int i = 1; i < s.length(); ++i) {
		//利用已知,Manacher核心
		if (i < c + dp[c])
			dp[i] = min(dp[2 * c - i], dp[c] + c - i);
		int left = i - dp[i];
		int right = i + dp[i];
		//向外扩展
		while (s[left] == s[right]) {
			--left;
			++right;
			++dp[i];
		}
		if (dp[c] + c < i + dp[i])
			c = i;
		if (dp[rc] < dp[i])
			rc = i;
	}
	//最大回文子串必然以#开头,以#结束,且#在奇数位置
	//如:[#a...a#],设left=res-dp[rc]+1和right=rc+dp[rc]-1分别为#开始和结束的坐标
	//那么转换为原数据str上的索引为start=left/2,end=right/2
	int start = (rc - dp[rc] + 1) / 2;
	int end = (rc + dp[rc] - 1) / 2;
	return str.substr(start, end - start);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值