leetcode 最长回文子串

本文探讨了三种高效算法:中心扩展法、动态规划及马拉车算法,用于解决字符串中最长回文子串的查找问题。通过实例演示,详细解析每种算法的实现原理与步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

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

示例 1:

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

示例 2:

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

方法1:以每个字符为中心依次搜索最长的回文子串 

string longestPalindrom(string s) {
	if (s.size() < 2) return s;
	int Maxlen = 0, start = 0;
	int s_len = s.size();
	for (int i = 0; i < s_len;) {
		if ((s_len - i) < Maxlen / 2) break;
		//如果剩余字符小于最长子串的一半,那么则不可能再出现比此时最长子串更长的回文子串
		int right = i, left = i;
		while (right < s_len - 1 && s[right] == s[right + 1]) right++;//为偶数个时要让right和left指向不同的字符
		i = right + 1;//如果前面是重复的字符则一定以他们为中心进行搜索,那么下一次搜索时应该从重复字符之后的字符进行搜索
		while (right < s_len-1 &&left > 0 && s[left-1] == s[right+1]) {//为奇数个时可以直接判断
			left--;
			right++;
		}
		if (Maxlen < right - left + 1) {
			Maxlen = right - left + 1;
			start = left;
		}
	}
	return s.substr(start, Maxlen);
}

方法2:动态规划,用一个二维数组dp[i][j]代表第i个字符到第j个字符是否为回文串。在判断过程中,如果i与j相邻则直接相等即可,如果,i与j不相邻,那么i到j是否为回文串,也与i+1到j-1是否为回文串有关

string longestPalindrom(string s) {
	if (s.empty()) return "";
	//int dp[s.size()][s.size()] = { 0 };会报错
	int **dp = new int*[s.size()];//动态申请变量空间
	for (int i = 0; i < s.size(); ++i) {
		dp[i] = new int[s.size()];
		memset(dp[i], 0, s.size());
	}
	//dp[i][j]中存储第i个字符到第j个字符是否回文串
	int left = 0, right = 0, len = 0;
	for (int i = 0; i < s.size(); i++) {
		dp[i][i] = 1;
		for (int j = 0; j < i; j++) {
			dp[j][i] = (s[j] == s[i] && (i - j < 2 || dp[j + 1][i - 1]));
			//此时的j到i是否为回文串,要求s[j] == s[i]并且相邻的位置也为回文串,如果ij相邻则不用判断相邻位置
			if (dp[j][i]&&len < i - j + 1) {//不仅要判断长度是否还要判断是否为回文串
				len = i - j + 1;
				left = j;
				right = i;
			}
		}
	}
	for (int i = 0; i < s.size(); i++)//用完之后一定要释放
		delete[]dp[i];
	delete[]dp;
	return s.substr(left, right - left + 1);

}

方法3:利用经典算法马拉车算法,相对而言技巧性较高。但是直接将算法复杂度由O(n^2)降至O(n)。其主要思路是先将字符串处理成根据普遍性的情况,那就是在每个字符的两边加上#。比如,bob加上#则为#b#o#b#,noon加上#字符则为#n#o#o#n#;这样就不用了再分别考虑回文子串是奇数个还是偶数个。同时,我们观察可以发现此时,回文串的半径正好等于未经处理回文串的长度。因此,在后期计算时,我们只用找经过处理之后的回文子串的半径即可。因为,最后我们要输出,最长的回文子串,所以我们不仅要知道子串的长度,还要知道它的起始位置在何处。这时,在处理好的字符前面加上一个不是字符串s中的也不是#的特殊字符,这样 (中心位置 - 处理后子串初始位置)/2 就是最小回文子串的初始位置。

在遍历寻找最长子串时,主要是利用回文的对称性。如下图所示,其中id代表当前最长子串的中心,i为当前搜索的中心下标,j为与i关于id对称的坐标值为i-2*id,mx为回文串的最右边。数组dp中存储的是以下标位置为中心的最大回文串半径

因此,当i<mx时,dp[i]应该等于mx-i或者是与其对称的j点处的最大半径中较小的一个

如果 当 i>mx时,此时我们就只能以i为中心逐位对比,计算回文串的长度

每次计算完以i为中心的最长子串时,要将id更新为最长子串中心,mx更新为最长子串的最右边

string Manacher(string s) {
//将字符串插入#为了方便处理在其头部插入任意不是#且不是s中的符号
	string t = "$#";
	for (int i = 0; i < s.size(); i++) {
		t += s[i];
		t += "#";
	}
	//用id代表当前最大回文串中心,mx代表回文串的最右边
	int  id = 0, mx = 0, resLen=0,resCen=0;
	vector<int> p(t.size(), 0);//用来存储每个以当前位置为中心的最大回文串长度的半径
	for (int i = 1; i < t.size(); i++) {
		p[i] = mx > i ? (min(p[2 * id - i], mx - i)) : 1;
		//如果mx比i大那么当前以i为中心的最大回文串长度半径为则为p[2 * id - i],mx - i中较小的一个
		//利用回文串的对称性,即与前面与i位置对称点的长度一致,或者是mx-i那么长 如果均不是那么假设为1且以i为中心逐个搜索
		while ( t[i +p[i]] == t[i - p[i]]) ++p[i];//以i为中心比较两边是否相等计算以i为中心的回文子串长度
		//如果最长边界超过mx则更新,id也要更新
		if (mx < i + p[i]) {
			mx = i + p[i];
			id = i;
		}
		if (resLen < p[i]) {
			resLen = p[i];
			resCen = i;
		}
	}
	//开始位置为中心位置减去长度/2,到长度位置
	return s.substr((resCen - resLen) / 2, resLen-1);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值