详解一下马拉车算法 Manache算法 使用c++

马拉车算法是寻找最长回文子串的高效算法,时间复杂度为O(n)

#include <iostream>
#include <string>
#include <vector>
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 <iostream>
#include <string>
#include <vector>

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 个不同回文。

  1. 预处理字符串的特性:
    • 在预处理后的字符串中,每个原始字符之间都插入了 '#'。
    • 这意味着预处理字符串的长度是原始字符串长度的两倍加一。
  2. 回文中心的两种情况:
    • 在原字符串中,回文中心可能在字符上,也可能在字符之间。
    • 在预处理字符串中,所有可能的回文中心都变成了字符位置(包括 '#')。
  3. 回文长度的对应关系:
    • 预处理字符串中长度为 1 的回文对应原字符串中长度为 1 的回文。
    • 预处理字符串中长度为 3 的回文对应原字符串中长度为 2 的回文。
    • 预处理字符串中长度为 5 的回文对应原字符串中长度为 3 的回文。
    • 以此类推...
  4. 计算公式的推导:
    • 设预处理字符串中的回文长度为 length。
    • 对应的原字符串回文长度为 (length + 1) / 2。
    • 例如:
      • length = 1 时,原字符串回文长度 = (1 + 1) / 2 = 1
      • length = 3 时,原字符串回文长度 = (3 + 1) / 2 = 2
      • length = 5 时,原字符串回文长度 = (5 + 1) / 2 = 3
  5. 为什么是不同的回文:
    • 在预处理字符串中,每个位置为中心的最长回文都是唯一的。
    • 这个最长回文包含了以该中心的所有较短回文。
  6. 举例说明: 考虑原字符串 "ababa" 和预处理字符串 "#a#b#a#b#a#":
    • 对于中心位置 5(原字符串中的中间 'a'):
      • 回文长度为 5,对应原字符串中 "ababa"
      • 回文长度为 3,对应原字符串中 "aba"
      • 回文长度为 1,对应原字符串中 "a"
    • 这三个回文都是不同的,且都以这个 'a' 为中心。
  7. 公式的应用:
    • 对于长度为 5 的回文,(5 + 1) / 2 = 3,正好对应这个中心位置的 3 个不同回文。

通过这种方式,(length + 1) / 2 既计算了原字符串中对应的回文长度,又恰好表示了以该位置为中心的不同回文数量。这个巧妙的关系使得我们可以快速计算所有回文子串的数量,而不需要逐一枚举。

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值