马拉车算法是寻找最长回文子串的高效算法,时间复杂度为O(n)
- #include
- #include
- #include
- using namespace std;
-
- string longestPalindrome(string s) {
- // 步骤1: 预处理,在字符间插入特殊字符'#'
- string t = "#";
- for (char c : s) {
- t += c;
- t += "#";
- }
-
- // 步骤2: 初始化变量
- int n = t.length();
- vector<int> P(n, 0); // P[i]存储以i为中心的回文半径
- int C = 0; // 当前回文串的中心
- int R = 0; // 当前回文串的右边界
- int maxLen = 0; // 最长回文子串的长度
- int centerIndex = 0; // 最长回文子串的中心位置
-
- // 步骤3: 主循环,计算每个位置的回文半径
- for (int i = 0; i < n; i++) {
- // 确定初始回文半径
- if (i < R) {
- int mirror = 2 * C - i; // i关于C的对称点
- P[i] = min(R - i, P[mirror]); // 利用对称性
- }
-
- // 尝试扩展回文
- int a = i + (1 + P[i]);
- int b = i - (1 + P[i]);
- while (a < n && b >= 0 && t[a] == t[b]) {
- P[i]++;
- a++;
- b--;
- }
-
- // 更新C和R
- if (i + P[i] > R) {
- C = i;
- R = i + P[i];
- }
-
- // 更新最长回文子串信息
- if (P[i] > maxLen) {
- maxLen = P[i];
- centerIndex = i;
- }
- }
-
- // 步骤4: 提取最长回文子串
- int start = (centerIndex - maxLen) / 2;
- return s.substr(start, maxLen);
- }
-
- int main() {
- string s = "babad";
- cout << "输入字符串: " << s << endl;
- cout << "最长回文子串: " << longestPalindrome(s) << endl;
- return 0;
- }
1.首先要进行预处理在每个字符之间添加"#",这样就可以统一处理奇数和偶数的字符串
2.变量有,回文半径组p,回文中心c,右边界r.
Manacher算法的核心思想是利用已经计算出的回文信息来避免重复计算。通过维护当前最右的回文边界,算法可以在大多数情况下直接得到一个很好的初始回文半径.
为了体现高继承性,下面我多使用了类来解答马拉车算法
- #include
- #include
- #include
-
- class Manacher {
- private:
- std::string original; // 原始字符串
- std::string processed; // 预处理后的字符串
- std::vector<int> palindromeLengths; // 回文长度数组
- int maxPalindromeLength; // 最长回文子串的长度
- int maxPalindromeCenter; // 最长回文子串的中心位置
-
- // 预处理字符串,在字符间插入特殊字符'#'
- void preprocess() {
- processed = "#";
- for (char c : original) {
- processed += c;
- processed += "#";
- }
- }
-
- // 核心Manacher算法
- void computePalindromeLengths() {
- int n = processed.length();
- palindromeLengths.resize(n, 0);
- int center = 0; // 当前回文串的中心
- int right = 0; // 当前回文串的右边界
-
- for (int i = 0; i < n; i++) {
- // 利用对称性初始化回文长度
- if (i < right) {
- int mirror = 2 * center - i;
- palindromeLengths[i] = std::min(right - i, palindromeLengths[mirror]);
- }
-
- // 尝试扩展回文
- int a = i + (1 + palindromeLengths[i]);
- int b = i - (1 + palindromeLengths[i]);
- while (a < n && b >= 0 && processed[a] == processed[b]) {
- palindromeLengths[i]++;
- a++;
- b--;
- }
-
- // 更新中心和右边界
- if (i + palindromeLengths[i] > right) {
- center = i;
- right = i + palindromeLengths[i];
- }
-
- // 更新最长回文子串信息
- if (palindromeLengths[i] > maxPalindromeLength) {
- maxPalindromeLength = palindromeLengths[i];
- maxPalindromeCenter = i;
- }
- }
- }
-
- public:
- // 构造函数
- Manacher(const std::string& s) : original(s), maxPalindromeLength(0), maxPalindromeCenter(0) {
- preprocess();
- computePalindromeLengths();
- }
-
- // 获取最长回文子串
- std::string getLongestPalindrome() {
- int start = (maxPalindromeCenter - maxPalindromeLength) / 2;
- return original.substr(start, maxPalindromeLength);
- }
-
- // 获取所有回文子串的数量
- int countAllPalindromes() {
- int count = 0;
- for (int length : palindromeLengths) {
- count += (length + 1) / 2;
- }
- return count;
- }
-
- // 判断子串是否为回文
- bool isPalindrome(int start, int end) {
- int len = end - start + 1;
- int center = start + end + 1; // 在processed字符串中的中心位置
- return palindromeLengths[center] >= len;
- }
- };
-
- int main() {
- std::string s = "babad";
- Manacher manacher(s);
-
- std::cout << "输入字符串: " << s << std::endl;
- std::cout << "最长回文子串: " << manacher.getLongestPalindrome() << std::endl;
- std::cout << "回文子串的总数: " << manacher.countAllPalindromes() << std::endl;
- std::cout << "子串'bab'是否为回文: " << (manacher.isPalindrome(0, 2) ? "是" : "否") << std::endl;
-
- return 0;
- }
最后让我详细解释一下为什么在预处理字符串中,每个回文长度对应原字符串中的 (length + 1) / 2 个不同回文。
通过这种方式,(length + 1) / 2 既计算了原字符串中对应的回文长度,又恰好表示了以该位置为中心的不同回文数量。这个巧妙的关系使得我们可以快速计算所有回文子串的数量,而不需要逐一枚举。