突破编程_C++_字符串算法(找到字符串中最长的回文子串)

1 算法题 :找到字符串中最长的回文子串

1.1 题目含义

找到字符串中最长的回文子串是一个经典的算法题。回文是指一个诗句、短语、单词、数字或其他字符序列,其从前往后和从后往前是完全相同的。因此,回文子串就是原字符串中的一个子序列,这个子序列从前往后读和从后往前读是一样的。题目要求找出给定字符串中最长的这样一个回文子串。

1.2 示例

示例 1:
输入: “babad”
输出: “bab” 或 “aba”
解释:在字符串 “babad” 中,“bab” 和 “aba” 都是回文子串,并且它们都是最长的回文子串,长度为 3。

示例 2:
输入: “cbbd”
输出: “bb”
解释:在字符串 “cbbd” 中,“bb” 是最长的回文子串,长度为2。

示例 3:
输入: “a”
输出: “a”
解释:单个字符本身就是一个回文,所以在这个例子中,整个字符串 “a” 就是最长的回文子串。

2 解题思路

解题思路如下:

对于找到字符串中最长的回文子串这个算法题,可以采用多种策略来解决。下面将分别描述中心扩展法、动态规划法和 Manacher 算法的基本解题思路。

(1)中心扩展法:

中心扩展法的基本思想是以每个字符或每对相邻字符为中心,向两边扩展,直到不能扩展为止,从而找到所有可能的回文子串。然后比较这些回文子串的长度,找出最长的那个。

具体步骤如下:

  • 遍历字符串中的每个字符,以其为回文中心进行扩展,查找奇数长度的回文子串。
  • 遍历字符串中每对相邻字符之间的空隙,以这两个字符的间隙为回文中心进行扩展,查找偶数长度的回文子串。
  • 在每次扩展过程中,比较当前扩展得到的回文子串长度与已知的最长回文子串长度,更新最长回文子串。

(2)动态规划法:

动态规划法通过构建一个二维数组来保存子问题的解,从而避免重复计算。对于回文子串问题,可以定义一个二维数组dp,其中dp[i][j]表示从索引i到索引j的子串是否是回文串。

具体步骤如下:

  • 初始化二维数组dp,将所有单个字符设为回文串(即dp[i][i] = true)。
  • 从长度为2的子串开始,逐步增加子串长度,遍历所有可能的子串。
  • 对于每个子串,检查其首尾字符是否相等,并且去掉首尾字符后的子串是否是回文串(即dp[i+1][j-1]是否为true)。如果满足条件,则当前子串是回文串。
  • 在遍历过程中,记录并更新找到的最长回文子串及其长度。

(3)Manacher 算法:

Manacher算法是一种线性时间复杂度的算法,用于解决最长回文子串问题。它利用了回文的性质,通过预处理和巧妙的转换,将问题转化为求解最长连续相同字符序列的问题。

具体步骤如下:

  • 对原始字符串进行预处理,在每个字符之间以及字符串首尾插入特殊字符(如’#'),使得所有回文子串的长度都变为奇数,从而简化问题。
  • 构建一个辅助数组,用于存储每个字符关于其所在回文中心的对称位置信息。
  • 从左到右遍历预处理后的字符串,同时维护当前回文中心及其对应的回文半径。
  • 对于每个字符,根据辅助数组中的信息,计算其对应的回文半径,并更新最长回文子串的信息。
  • 最后,根据最长回文子串的信息和预处理时添加的特殊字符,还原得到原始字符串中的最长回文子串。

3 算法实现代码

3.1 使用中心扩展法

如下为算法实现代码:

#include <iostream>  
#include <string>  
#include <vector>  
#include <algorithm>  

class Solution
{
public:
	// 找到最长回文子串  
	std::string longestPalindrome(std::string s) {
		int start = 0, maxLength = 0; // 存储最长回文子串的起始位置和长度  
		int n = s.size();

		// 遍历每个字符,以它为中心点进行扩展  
		for (int i = 0; i < n; i++) {
			// 以当前字符为中心点的奇数长度回文串  
			int oddLength = expandFromCenter(s, i, i);
			// 以当前字符和下一个字符的间隙为中心点的偶数长度回文串  
			int evenLength = expandFromCenter(s, i, i + 1);

			// 取两者中较长的一个作为当前中心点的最大回文串长度  
			int currLength = std::max(oddLength, evenLength);

			// 如果当前回文串长度大于已知的最长回文串长度,则更新最长回文串信息  
			if (currLength > maxLength) {
				start = i - (currLength - 1) / 2; // 更新最长回文串的起始位置  
				maxLength = currLength; // 更新最长回文串的长度  
			}
		}

		// 返回最长回文子串  
		return s.substr(start, maxLength);
	}

private:
	// 函数用于扩展回文串,返回回文串的长度  
	int expandFromCenter(const std::string& s, int left, int right) {
		while (left >= 0 && right < s.size() && s[left] == s[right]) {
			left--;
			right++;
		}
		// 返回回文串的长度,注意这里需要加1,因为left和right在循环中分别多减和多加了一次  
		return right - left - 1;
	}
};

上面代码定义了一个私有辅助函数 expandFromCenter,该函数以给定的左右索引为中心,尝试扩展回文串,并返回扩展后的回文串长度。然后,在 longestPalindrome 函数中,遍历字符串的每个字符,分别以该字符和该字符与下一个字符的间隙为中心,调用 expandFromCenter 函数来寻找可能的回文串。然后比较奇数长度和偶数长度的回文串,并更新最长回文串的起始位置和长度。最后,根据最长回文串的起始位置和长度,从原始字符串中截取并返回最长回文子串。

调用上面的算法,并得到输出:

int main() 
{
	Solution s;
	std::string str = "babad";
	std::string longestPalin = s.longestPalindrome(str);
	std::cout << "The longest palindromic substring is: " << longestPalin << std::endl;
	return 0;
}

上面代码的输出为:

The longest palindromic substring is: bab

3.2 使用动态规划法

如下为算法实现代码:

#include <iostream>  
#include <string>  
#include <vector>  
#include <algorithm>  

class Solution
{
public:
	// 找到最长回文子串  
	std::string longestPalindrome(std::string s) {
		int n = s.size();
		if (n < 2) return s;

		// dp[i][j]表示s[i..j]是否是回文串  
		std::vector<std::vector<bool>> dp(n, std::vector<bool>(n, false));
		int start = 0, maxLength = 1; // 记录最长回文子串的起始位置和长度  

		// 初始化单个字符为回文串  
		for (int i = 0; i < n; ++i) {
			dp[i][i] = true;
		}

		// 从长度为2的子串开始判断是否为回文串  
		for (int len = 2; len <= n; ++len) {
			for (int i = 0; i < n - len + 1; ++i) {
				int j = i + len - 1;
				if (s[i] == s[j] && (len == 2 || dp[i + 1][j - 1])) {
					dp[i][j] = true;
					// 更新最长回文子串的信息  
					if (len > maxLength) {
						start = i;
						maxLength = len;
					}
				}
			}
		}

		// 返回最长回文子串  
		return s.substr(start, maxLength);
	}
};

上面代码创建了一个二维布尔值数组 dp,其中 dp[i][j] 表示从索引i到索引j的子串是否是回文串。首先初始化所有单个字符为回文串,然后逐步增加子串的长度,检查所有可能的子串。

对于每个子串,检查其首尾字符是否相等,并且去掉首尾字符后的子串(如果存在)是否是回文串。如果这两个条件都满足,那么当前子串就是回文串。

在遍历过程中,还跟踪了最长回文子串的起始位置和长度,以便在算法结束时能够返回正确的结果。

最后,返回从最长回文子串的起始位置开始,长度为最长回文子串长度的子串作为结果。

4 测试用例

以下是针对上面算法的测试用例,基本覆盖了各种情况:

(1)基础测试用例:

输入: “babad”
输出: “bab” 或 “aba”
说明: 输入字符串中包含两个长度为3的回文子串 “bab” 和 “aba”。

输入: “cbbd”
输出: “bb”
说明: 输入字符串中有一个长度为2的回文子串 “bb”。

输入: “a”
输出: “a”
说明: 单个字符本身就是一个回文串。

(2)边界测试用例:

输入: “”
输出: “”
说明: 空字符串不包含任何回文子串。

输入: “aaaaaaa”
输出: “aaaaaaa”
说明: 整个字符串是一个回文串。

(3)复杂测试用例:

输入: “saturdayinightisfun”
输出: “ini”
说明: 输入字符串中包含两个长度为3的回文子串 “ini” 。

(4)特殊字符测试用例:

输入: “!”#$%&'()*+,-./:;<=>?@[\]^_{|}~"`
输出: 任意单个字符
说明: 输入字符串只包含特殊字符,每个字符单独都是回文串。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值