首页 > 编程语言 >马拉车算法(C/C++)

马拉车算法(C/C++)

时间:2024-10-22 22:52:51浏览次数:3  
标签:int C++ maxLen 算法 字符串 拉车 半径 回文

#1024程序员节 | 征文#

马拉车算法(Manacher's Algorithm)是一种用于在字符串中查找最长回文子串的线性时间复杂度算法。该算法由Udi Manacher在1980年代提出,因此得名。它的核心思想是利用已知的回文信息来减少不必要的比较,从而提高效率。

算法步骤

  1. 预处理字符串: 为了处理奇数长度和偶数长度的回文串,算法首先对输入字符串进行预处理。在每个字符之间插入一个特殊字符(通常是#),并在字符串的开头和结尾也各插入一个。这样做的目的是为了让每个字符都有一个唯一的中心位置。

  2. 初始化变量

    • p[]:一个数组,用于存储每个位置的回文半径。
    • C:当前考虑的中心位置。
    • R:当前考虑的右端点,即以C为中心的回文串的最右端。
  3. 遍历字符串: 对于预处理后的字符串中的每个位置i,算法尝试找到以i为中心的回文串的半径。这个过程分为两步:

    • 利用已知的回文信息:如果i在当前考虑的回文串内(即i < R),则可以利用对称位置的回文半径来减少比较次数。
    • 扩展回文串:如果i不在当前考虑的回文串内,或者需要进一步扩展回文串,算法会尝试扩展以i为中心的回文串,直到无法进一步扩展。
  4. 更新最大回文串: 在遍历过程中,如果找到的回文串半径大于之前记录的最大半径,则更新最大回文串的起始位置和半径。

  5. 提取最长回文子串: 遍历完成后,根据记录的最大半径和起始位置,从原始字符串中提取最长的回文子串。


算法详解

  • 中心扩展思想:马拉车算法利用了中心扩展的思想,即以每个字符为中心,向两边扩展,直到无法形成回文串为止。这种方法可以避免重复比较相同的字符对。

  • 利用对称性:算法利用了回文串的对称性,即如果以i为中心的回文串已知,那么以2*C - i为中心的回文串的半径至少与以C为中心的回文串的半径相同。这里C是当前考虑的中心位置。

  • 动态规划:虽然马拉车算法不是传统意义上的动态规划算法,但它的思想与动态规划类似,即利用之前计算的结果来简化当前的计算。


复杂度分析

  • 时间复杂度:O(n),其中n是输入字符串的长度。这是因为每个位置的回文半径只计算一次。
  • 空间复杂度:O(n),用于存储回文半径的数组。

代码实现:

#include <iostream>
#include <vector>
#include <string>
using namespace std;

string manacher(const string& s) {
    if (s.empty()) return s;
    
    // 预处理字符串,插入特殊字符
    string t = "#";
    for (char c : s) {
        t += c;
        t += "#";
    }
    int n = t.length();
    vector<int> p(n, 0);
    int C = 0, R = 0; // C为当前回文的中心,R为当前回文的右边界
    int maxLen = 0, centerIndex = 0;
    
    for (int i = 1; i < n; i++) {
        if (i < R) {
            int mirror = 2 * C - i;
            p[i] = min(R - i, p[mirror]);
        }
        int left = i - p[i], right = i + p[i];
        while (left >= 1 && right < n - 1 && t[left - 1] == t[right + 1]) {
            p[i]++;
            left--;
            right++;
        }
        if (i + p[i] > R) {
            C = i;
            R = i + p[i];
        }
        if (p[i] > maxLen) {
            maxLen = p[i];
            centerIndex = i;
        }
    }
    
    // 从预处理的字符串中提取最长回文子串
    int start = (centerIndex - maxLen) / 2;
    return s.substr(start, maxLen - 1); // 减1是因为预处理字符串中插入了特殊字符
}

int main() {
    string s;
    cin >> s;
    cout << manacher(s) << endl;
    return 0;
}


应用实例:

在LeetCode上的题目“5. Longest Palindromic Substring”就是一个典型的应用实例,要求找出给定字符串中的最长回文子串。马拉车算法可以应用于生物信息学中DNA序列的回文结构分析,以及文本处理中的回文检测等场景。该算法也可以用于解决其他与回文相关的问题,如找出所有回文子串、判断回文对等。

算法实现部分:
string manacher(string s) {
    string t = "#";
    for (char c : s) {
        t += c;
        t += "#";
    }
    vector<int> p(t.size(), 0);
    int C = 0, R = 0;
    int maxLen = 0, centerIndex = 0;
    for (int i = 1; i < t.size(); i++) {
        if (i < R) p[i] = min(p[2 * C - i], R - i);
        else p[i] = 1;
        while (t[i + p[i]] == t[i - p[i]]) p[i]++;
        if (i + p[i] > R) {
            C = i;
            R = i + p[i];
        }
        if (p[i] > maxLen) {
            maxLen = p[i];
            centerIndex = i;
        }
    }
    int start = (centerIndex - maxLen) / 2;
    return s.substr(start, maxLen - 1);
}

马拉车算法是解决最长回文子串问题的有效方法,其线性时间复杂度使其在处理大规模数据时具有明显优势。文章到此为止感谢大家的支持。

标签:int,C++,maxLen,算法,字符串,拉车,半径,回文
From: https://blog.csdn.net/2401_86771711/article/details/143170436

相关文章

  • C++入门Day5 ~ 6:简单变量 & 数据类型 part 1 <8000字长文带你初步理解数据类型>
    这是我在学习中的一个小问题,希望对你也有所帮助:        问:数据类型和简单变量属于oop的基本概念吗?        答:不是!数据类型和简单变量本身并不属于面向对象编程(OOP)的基本概念,但它们是编程中的基础概念,面向对象编程会基于这些基础概念来构建更复杂的结构。......
  • 算法复杂度
    文章目录一、算法复杂度的概念二、时间复杂度1.大O的渐进表示法三、空间复杂度提示:以下是本篇文章正文内容,下面案例可供参考一、算法复杂度的概念算法在编写成可执⾏程序后,运⾏时需要耗费时间资源和空间(内存)资源。因此衡量⼀个算法的好坏,⼀般是从时间和空间两......
  • C++基础——寻找水仙花数
    C++基础——寻找水仙花数一、水仙花数1.什么是水仙花数2.编译思路二、头文件三、主文件四、整体代码五、运行结果一、水仙花数1.什么是水仙花数原理:我们先理解什么是水仙花数(NarcissisticNumber),所谓的水仙花数是指一个整数的各位数字的立方和等于它本身。例如153......
  • 【C++】踏上C++学习之旅(三):“我“ 与 “引用“ 的浪漫邂逅
    文章目录前言1."引用"的概念1.1"引用"的语法2."引用"的特性3."引用"的使用场景3.1"引用"做参数3.2"引用"做返回值3.2.1"引用"做返回值时需要注意的点4.常引用5."引用"在底层的实现6."引用"和"指针"的不同点(面试常考)前言本文会着重的讲解&q......
  • 代码随想录算法训练营day22和day23 | 77. 组合 216.组合总和III 17.电话号码的字母
    学习资料:https://programmercarl.com/回溯算法理论基础.html回溯法backtracking:for循环控制递归数量,暴力搜索:组合、切割、子集、排列、棋盘今天学了组合和切割可以画个N叉树的图来帮助理解回溯过程组合又包括1.单个数组(要加startIndex参数)或多个数组;2.数组内有无重复元素;3.数......
  • 【C++-NOIP篇-4】 [NOIP2007 普及组] 纪念品分组
    文章目录[NOIP2007普及组]纪念品分组题目背景题目描述输入格式输出格式样例#1样例输入#1样例输出#1提示题目思路完整Code[NOIP2007普及组]纪念品分组题目背景NOIP2007普及组T2题目描述元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参......
  • Spark实现PageRank算法
    详细步骤:1、创建Sparksql环境2、读取数据3、数据切分(分为page列,outLink列)形成表pageDF4、新增pr一列 (给定初始值)  形成表initPrDF5、新增avgPr一列(根据出链关系,求每个页面所分到的Pr)6、分组聚合(将outLink列explode炸开,在按照page分组,然后sum求和,这就是表......
  • 施磊c++基础8
    STL内容学习简介C++STL:standardtemplatelibarayvector容器底层数据结构:动态开辟的数组。每次以空间大的二倍扩容增加vec.push_back(20);末尾添加元素20—O(1)vec.insert(it,20);在it迭代器指向的位置插入元素20—O(n)删除vec.pop_back;末尾删除元素----......
  • 施磊c++基础7
    C++的四种类型转换c语言中提供的类型强转inta=(int)b;c++提供:const_cast:去掉常量属性的一个类型转换 int*p1=(int*)&a; int*p2=const_cast<int*>(&a);这两句是一样的,只不过使用第二种,可以保证类型转换是安全的,如果要转换成不符合的类型就会报错。static_......
  • 【C++指南】类和对象(四):类的默认成员函数——全面剖析 : 拷贝构造函数
     引言拷贝构造函数是C++中一个重要的特性,它允许一个对象通过另一个已创建好的同类型对象来初始化。了解拷贝构造函数的概念、作用、特点、规则、默认行为以及如何自定义实现,对于编写健壮和高效的C++程序至关重要。 C++类和对象系列文章,可点击下方链接阅读:【C++指南......