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


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

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. string longestPalindrome(string s) {
    6. // 步骤1: 预处理,在字符间插入特殊字符'#'
    7. string t = "#";
    8. for (char c : s) {
    9. t += c;
    10. t += "#";
    11. }
    12. // 步骤2: 初始化变量
    13. int n = t.length();
    14. vector<int> P(n, 0); // P[i]存储以i为中心的回文半径
    15. int C = 0; // 当前回文串的中心
    16. int R = 0; // 当前回文串的右边界
    17. int maxLen = 0; // 最长回文子串的长度
    18. int centerIndex = 0; // 最长回文子串的中心位置
    19. // 步骤3: 主循环,计算每个位置的回文半径
    20. for (int i = 0; i < n; i++) {
    21. // 确定初始回文半径
    22. if (i < R) {
    23. int mirror = 2 * C - i; // i关于C的对称点
    24. P[i] = min(R - i, P[mirror]); // 利用对称性
    25. }
    26. // 尝试扩展回文
    27. int a = i + (1 + P[i]);
    28. int b = i - (1 + P[i]);
    29. while (a < n && b >= 0 && t[a] == t[b]) {
    30. P[i]++;
    31. a++;
    32. b--;
    33. }
    34. // 更新C和R
    35. if (i + P[i] > R) {
    36. C = i;
    37. R = i + P[i];
    38. }
    39. // 更新最长回文子串信息
    40. if (P[i] > maxLen) {
    41. maxLen = P[i];
    42. centerIndex = i;
    43. }
    44. }
    45. // 步骤4: 提取最长回文子串
    46. int start = (centerIndex - maxLen) / 2;
    47. return s.substr(start, maxLen);
    48. }
    49. int main() {
    50. string s = "babad";
    51. cout << "输入字符串: " << s << endl;
    52. cout << "最长回文子串: " << longestPalindrome(s) << endl;
    53. return 0;
    54. }

    1.首先要进行预处理在每个字符之间添加"#",这样就可以统一处理奇数和偶数的字符串

    2.变量有,回文半径组p,回文中心c,右边界r.

    Manacher算法的核心思想是利用已经计算出的回文信息来避免重复计算。通过维护当前最右的回文边界,算法可以在大多数情况下直接得到一个很好的初始回文半径.

    为了体现高继承性,下面我多使用了类来解答马拉车算法

    1. #include
    2. #include
    3. #include
    4. class Manacher {
    5. private:
    6. std::string original; // 原始字符串
    7. std::string processed; // 预处理后的字符串
    8. std::vector<int> palindromeLengths; // 回文长度数组
    9. int maxPalindromeLength; // 最长回文子串的长度
    10. int maxPalindromeCenter; // 最长回文子串的中心位置
    11. // 预处理字符串,在字符间插入特殊字符'#'
    12. void preprocess() {
    13. processed = "#";
    14. for (char c : original) {
    15. processed += c;
    16. processed += "#";
    17. }
    18. }
    19. // 核心Manacher算法
    20. void computePalindromeLengths() {
    21. int n = processed.length();
    22. palindromeLengths.resize(n, 0);
    23. int center = 0; // 当前回文串的中心
    24. int right = 0; // 当前回文串的右边界
    25. for (int i = 0; i < n; i++) {
    26. // 利用对称性初始化回文长度
    27. if (i < right) {
    28. int mirror = 2 * center - i;
    29. palindromeLengths[i] = std::min(right - i, palindromeLengths[mirror]);
    30. }
    31. // 尝试扩展回文
    32. int a = i + (1 + palindromeLengths[i]);
    33. int b = i - (1 + palindromeLengths[i]);
    34. while (a < n && b >= 0 && processed[a] == processed[b]) {
    35. palindromeLengths[i]++;
    36. a++;
    37. b--;
    38. }
    39. // 更新中心和右边界
    40. if (i + palindromeLengths[i] > right) {
    41. center = i;
    42. right = i + palindromeLengths[i];
    43. }
    44. // 更新最长回文子串信息
    45. if (palindromeLengths[i] > maxPalindromeLength) {
    46. maxPalindromeLength = palindromeLengths[i];
    47. maxPalindromeCenter = i;
    48. }
    49. }
    50. }
    51. public:
    52. // 构造函数
    53. Manacher(const std::string& s) : original(s), maxPalindromeLength(0), maxPalindromeCenter(0) {
    54. preprocess();
    55. computePalindromeLengths();
    56. }
    57. // 获取最长回文子串
    58. std::string getLongestPalindrome() {
    59. int start = (maxPalindromeCenter - maxPalindromeLength) / 2;
    60. return original.substr(start, maxPalindromeLength);
    61. }
    62. // 获取所有回文子串的数量
    63. int countAllPalindromes() {
    64. int count = 0;
    65. for (int length : palindromeLengths) {
    66. count += (length + 1) / 2;
    67. }
    68. return count;
    69. }
    70. // 判断子串是否为回文
    71. bool isPalindrome(int start, int end) {
    72. int len = end - start + 1;
    73. int center = start + end + 1; // 在processed字符串中的中心位置
    74. return palindromeLengths[center] >= len;
    75. }
    76. };
    77. int main() {
    78. std::string s = "babad";
    79. Manacher manacher(s);
    80. std::cout << "输入字符串: " << s << std::endl;
    81. std::cout << "最长回文子串: " << manacher.getLongestPalindrome() << std::endl;
    82. std::cout << "回文子串的总数: " << manacher.countAllPalindromes() << std::endl;
    83. std::cout << "子串'bab'是否为回文: " << (manacher.isPalindrome(0, 2) ? "是" : "否") << std::endl;
    84. return 0;
    85. }

    最后让我详细解释一下为什么在预处理字符串中,每个回文长度对应原字符串中的 (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 既计算了原字符串中对应的回文长度,又恰好表示了以该位置为中心的不同回文数量。这个巧妙的关系使得我们可以快速计算所有回文子串的数量,而不需要逐一枚举。

  • 相关阅读:
    代码随想录二刷 day01 | 704. 二分查找 27. 移除元素 26.删除有序数组中的重复项 80. 删除有序数组中的重复项 II
    每天五分钟机器学习:如何解决欠拟合问题
    SPL比SQL更难了还是更容易了?
    以任意位置中间元素翻转字符串:
    Qt,python获取IP地址信息
    免费在线真好用的思维脑图
    Go语言用Colly库编写的图像爬虫程序
    CronExpression
    Spring Cloud Gateway从数据库读取路由配置
    PyQt5快速开发与实战 9.4 Matplotlib在PyQt中的应用
  • 原文地址:https://blog.csdn.net/m0_61862494/article/details/140413881