LeetCode5.最长回文子串————Manacher算法

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

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

示例 2:

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

方法一: 动态规划,O(n^2)

在这里插入图片描述

i 为左指针,j 为右指针。
dp[ i ][ j ]指:从i到j位置组成的字串是否回文,回文则1,否则0。
若str[ i ] = str[ j ],则dp[ i ][ j ]是否回文取决于dp[ i + 1][ j - 1]是否回文。
若str[ i ] != str[ j ],则dp[ i ][ j ]肯定不回文。
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
class Solution {
public:
	//O(n^2)动态规划
	string longestPalindrome(string s) {
		int i, j;
		vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));//保存i到j子串是否回文
		int maxI = 0, maxJ = 0;//保存最大回文子串的两端位置i与j
		int maxNums = 1;//保存最大回文子串的字符数
		右指针先定位
		for (j = 0; j < s.size(); ++j)
		{
			dp[j][j] = true;
			//左指针基于右指针,往左移动,这样可以利用之前算出来的值(这里脑洞有点大)
			for (i = j - 1; i >= 0; --i)
			{
				if (s[i] == s[j])//如果两端字符相等
				{
					if (j - i == 1 || dp[i + 1][j - 1] != false)//如果两端字符相邻 - 或者 - 两端字符内部的两端仍是相等字符
					{
						dp[i][j] = true;//从i到j位置的字串回文
						if (maxNums < j - i + 1)//获取最大回文子串
						{
							maxNums = j - i + 1;
							maxI = i;
							maxJ = j;
						}
					}
					else
						dp[i][j] = false;
				}
				//如果两端字符不相等
				else
					dp[i][j] = false;//从i到j位置的字串不回文
			}
		}
		return s.substr(maxI, maxJ - maxI + 1);
	}
};

方法二: Manacher算法,O(n)

马拉车算法:【O(N),不断扩大R圈,直至最后一个字符】
用于计算字符串中最大的回文子串

添加虚位
计算回文半径数组
可能性:

  1. i 在当前 R 外面 =>> 暴力直接扩 R 【暴力循环比较判断,以 i 为中心进行回文判断】
  2. i 在当前 R 内(i’i 围绕当前中心 C 作的 [左边] 对称点)
    • (1)i’ 位置的回文半径圈没有超过当前 R 圈=>> i 位置的回文半径等于 i’ 的回文半径 【O(1)】
    • (2)i’ 位置的回文半径圈超过了当前 R 圈 =>> i 位置的回文半径等于 i 位置到当前 R 位置 【O(1)】
    • (3)i’ 位置的回文半径圈正好位于当前R圈 =>> i 位置到 R 位圈位置不用检测,以后需要进行比较检测,扩大当前最长回文半径 R,修改当前最长回文中心 C 【暴力循环比较判断,以i为中心进行回文判断】
//马拉车算法
	string longestPalindrome(string s) {
		//结果变量
		int resultCenter = 0;//结果的回文中心位置
		int resultMaxRadius = 0;//结果的最大回文半径值

		//添加虚位‘#’
		string sInput(1, '#');
		for (int i = 0; i < s.size(); ++i)
		{
			sInput += s[i];
			sInput.append("#");
		}

		vector<int> radiusStr(sInput.size(), 0);//回文半径数组 【int radiusStr[sInput.size()] = { 0 };不出错】
		int rightCenter = -1;//当前最右?回文有边界的最早出现的回文中心 C
		int rightBorder = -1;//当前最右?回文有边界位置 R

		//循环计算每个位置的回文半径
		for (int i = 0; i < sInput.size(); ++i)
		{
			//当前位置在最右边界外面,进行暴力扩大判断当前位置的回文
			if(i >= rightBorder)
			{
				int j = 1;
				//暴力判断当前位置的回文
				while(i + j < sInput.length() && i - j > -1)
				{
					if (sInput[i + j] == sInput[i - j])
						++radiusStr[i];
					else
						break;
					++j;
				}
				//判断是否修改最右边界位置值与中心值
				//判断条件为最右边界是否被扩大
				if(i + radiusStr[i] > rightBorder)
				{
					rightBorder = i + radiusStr[i];
					rightCenter = i;
				}
			}else
			{
				int i_symmetry = rightCenter - (i - rightCenter);//i位置以rC为中心的对称点 => i'
				if(i_symmetry >= 0)//对称点是存在的
				{
					int i_symmetry_radius = radiusStr[i_symmetry];//获取对称点的回文半径
					//rightBorder - i:最右回文右边界到i的距离
					//i_symmetry_radius:i对称点i’的回文半径
					//判断两个距离哪个更大
					if(i_symmetry_radius > rightBorder - i)
					{
						radiusStr[i] = rightBorder - i;
					}else if(i_symmetry_radius < rightBorder - i)
					{
						radiusStr[i] = radiusStr[i_symmetry];
					}else if(i_symmetry_radius == rightBorder - i)
					{
						//i到最右边界R之间无需判断    
						radiusStr[i] = i_symmetry_radius;
						//从R+1开始暴力判断
						int j = i_symmetry_radius + 1;
						//暴力扩大
						while (i + j < sInput.length() && i - j > -1)
						{
							if (sInput[i + j] == sInput[i - j])
								++radiusStr[i];
							else
								break;
							++j;
						}
						//判断是否修改最右边界位置值与中心值
						//判断条件为最右边界是否被扩大
						if (i + radiusStr[i] > rightBorder)
						{
							rightBorder = i + radiusStr[i];
							rightCenter = i;
						}
					}
				}
			}
			//每次循环都保存最大回文半径的中心位置
			if(resultMaxRadius < radiusStr[i])
			{
				resultMaxRadius = radiusStr[i];
				resultCenter = i;
			}
		}

		//最后得出最长回文子串
		return s.substr(resultCenter / 2 - resultMaxRadius / 2, resultMaxRadius);
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值