用马拉车算法寻找字符串的最长回文子串

字符串的最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。例如输入: “cbbd”,输出: “bb”。

1.暴力解法

预处理

字符串分为奇数长度和偶数长度,例如奇数长度字符串“abcba”是以c为对称轴,偶数长度字符串“cbbc”是以bb为对称轴。为了扩充成奇数长度,“abcba”变成“#a#b#c#b#a#”,“cbbc”变成“#c#b#b#c#”。如果不说明原始回文子串,以下回文子串指的都是经过处理后的子串。

string process(string s) {
	string ss = "#";
	int n = s.size();
	for (int i = 0; i < n; i++)  //构造新字串
		ss = ss + s[i] + "#";
	return ss;
}

以基点往两边扩充

变量:
i为基点位置
j为相对于i的距离
arr2数组保存对应位置的原始回文子串长度。
过程:
遍历字符串的每个点,以基点i为中心往外扩充,假设移动j位置,检查s[i+1+j]与 s[i-1-j]是否相同。相同则j++,不同则退出循环,将回文长度保存在arr2数组中。arr2数组如下,每个位置保存的是原始数组的回文长度,是不包括#的。

# a # b # c # b # a #
0 1 0 1 0 3 0 1 0 1 0

得到最长回文字串

遍历arr2数组,则最大位置则为回文字串中心center,值为最大长度len。
变换到原始字符串的起点,start=center/2-len/2。

暴力解法代码

string baoli(string s) {
	string s2 = process(s);
	
	int len2 = s2.size();
	cout << s2 << endl;

	
	vector<int> arr2(len2); //保存该位置的回文长度

	for (int i = 0; i < len2; i++) {
		int j = 0;
		while (i - j > -1 && i + j < len2 && s2[i+1+j]==s2[i-1-j]) {
			j++;
		}
		arr2[i] = j;
	}

	int res = INT_MIN;
	int start;
	for (int i = 0; i < len2; i++) {
		if (arr2[i] > res) {
			res = arr2[i];
			start = (i - res) / 2;
		}
	}
	return s.substr(start, res);
}

1.马拉车解法

暴力解法的时间复杂度是O(n^2),马拉车能够优化到O(n)时间。

核心:用一个最右边界R记录以center为中心所能到达的回文最右范围

i^ center i R
# a # b # c # b # a #
0 1 0 1 0 3 0 1 0 1 0

接下来,在求center之后R之前i点的回文长度,直接利用其关于center的对称点i^的回文长度len信息即可。
还没结束,由于回文长度len与R的关系不能确定,要分为以下三种情况:

  • i+len
  • i+len=R: i的回文长度还需继续往外扩充才能确定
  • i+len>R: i的回文长度还需继续往外扩充才能确定
    但是实际上在代码中,对以上三种情况做了统一,j=min(len,R-i),再接着往外扩充即可。

总结

马拉车算法的代码只是在暴力解法做了些许改进,即先选寻找对称点的信息再接着往外扩充,时间上却又有大幅提升。

马拉车解法代码

#include
using namespace std;

string longestPalindrome(string s) {
	if (s.empty())
		return "";

	string ss = process(s);

	int c = -1;
	int r = -1;
	int len = ss.size();
	vector<int> pArr(len);

	for (int i = 0; i != len; i++) {
		int j = 0;
		if (i < r)
			j = min(pArr[2 * c - i], r-i);
		while (i + j < len && i - j>-1 && ss[i + j] == ss[i - j]) {
				j++;
		}
		pArr[i] = j - 1;
		//对右边界做更新,对center做更新
		if (i + pArr[i] > r) {
			r = i + pArr[i];
			c = i;
		}

	}

	int res = INT_MIN;
	int start;
	for (int i = 0; i < len; i++) {
		if (pArr[i] > res) {
			res = pArr[i];
			start = (i - res) / 2;
		}
	}
	return s.substr(start, res);
}


int main() {
	cout << longestPalindrome("babadada");

	system("pause");
	return 0;
}

你可能感兴趣的:(用马拉车算法寻找字符串的最长回文子串)