首页 > 编程语言 >「字符串」Manacher算法(马拉车)/ LeetCode 05(C++)

「字符串」Manacher算法(马拉车)/ LeetCode 05(C++)

时间:2024-07-10 22:29:30浏览次数:12  
标签:-------- 字符 05 Manacher 复杂度 C++ ii 字符串 回文

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

示例 2:

输入:s = "cbbd"
输出:"bb"
思路

我们回想中心扩散法:某字符处的中心扩散完毕后,其实已经将它身前身后的字符段落都搜索过了,那么如果我们搜索其后的字符能够利用这一点,能否避免不必要的搜索来达到线性时间复杂度O(n)呢?

我们不妨先研究比较简单的一种情况:回文子串只有一个回文中心,是一个奇字符串(后续我们可以用一些方法将奇偶字符串全部处理成奇字符串)

观察这段字符:

           --------x--------
             --a--     
char[i]: f t b a b x b a b t g
     i : 0 1 2 3 4 5 6 7 8 9 10
 Now i :           c   |

当我们来到char[7]='a'位置时,我们发现他被此前的以'x'为中心的当前最长回文串覆盖住了。

这表明我们可以用此处的以'x'为中心对称点来快速判断而不需要进行暴力扩散。

我们将'x'所在位置称为c,这是我们的回文中心;数组r[i]记录每个点的回文半径(回文半径=本身+扩散距离,如r[c]=5)容易发现i=7的对称点ii=c-(i-r[c])=3。

我们发现ii的回文长度被c的回文长度完全覆盖,这意味着i的回文长度和ii是相等的,那么直接r[i]=r[ii]即可快速得到i的回文长度。

在进行遍历的同时维护中心c,如果某字符在c的屋檐下,那么可以使用回文串的对称性质来避免重复的暴力扩散(毕竟为了得到c的回文长度时我们进行过暴力扩散了),这就是Manacher算法的思想。

只在得到中心c时进行暴力扩散,后续的字符通过c来判断,这样我们就保证了c身前身后的字符只被遍历了一次,因此时间复杂度是O(n)。

解题过程

我们有两个工作要做:一是处理字符串使其称为奇字符串(只有一个中心字符);二是判断i的对称位置ii的三种情况

首先对字符串进行插值处理:

string ss="#";
for(int i=0;i<s.size();i++){
ss.push_back(s[i]);
ss.push_back('#');
}
//s="abacc"则ss="#a#b#a#c#c#"

原字符串的任意回文子串长度为len,新字符串的长度为2*len+1,必然是奇字符串。
这一步的时间复杂度为O(n),有限个线性时间复杂度加和仍是线性的。

接下来开始判断三种情况:

  1. i=7的对称点ii=3的回文半径被r[c]完全覆盖
               --------x--------
                 --a--     
    char[i]: f t b a b x b a b t g
         i : 0 1 2 3 4 5 6 7 8 9 10
     Now i :           c   |
    

    此时r[i]=r[ii],直接continue循环

  2.  i=7的对称点ii=3的回文半径超出r[c]
               --------x--------
             ------a------     
    char[i]: b x b a b x b a b x g
         i : 0 1 2 3 4 5 6 7 8 9 10
     Now i :           c   |

    此时对r[ii]进行截断,r[i]=ii-(c-r[c])。
    这是因为如果r[i]==r[ii],当初暴力扩散r[c]的时候得到的结果不可能使r[ii]超出r[c],r[i]==r[ii]时得到的r[c]长度即为情况1)

  3. i=7的对称点ii=3的回文半径恰好压线
               --------x--------
               ----a----
                     ------a------     
    char[i]: f x b a b x b a b x b
         i : 0 1 2 3 4 5 6 7 8 9 10
     Now i :           c   |

    此时我们只能知道r[i]至少等于r[ii],此后应进行暴力扩散

最后,如果我们得到的新回文半径比r[c]长,那么应该令c=i来确保c的r[c]的全局最大性。
至于i>c+r[c]-1?(i不在最长回文串覆盖下)直接暴力扩散就好了。

得到结果后利用string::substr(int pos,int n)(从pos开始的n个字符)方法截取以c为中心的全局最长回文串,过滤掉插值字符'#'后输出答案即可。

复杂度

时间复杂度: O(n)

空间复杂度: O(n)

经测试Manacher算法击败全世界

Code
class Solution {
public:
    string longestPalindrome(string s) {
        string ss="#";
        for(int i=0;i<s.size();i++){ss.push_back(s[i]);ss.push_back('#');}//处理字符串
        int c=0,r[2001]={1,};
        for(int i=1;i<ss.size();i++){
            int j=1;//j是我们用于的暴力扩散的回文半径
            if(i<=c+r[c]-1){//i在当前最长回文串中
               int ii=c-(i-c);//↓以下为讨论三种情况
               if(ii-r[ii]>c-r[c]){r[i]=r[ii];continue;}//r[ii]被r[c]完全覆盖
               else if(ii-r[ii]<c-r[c]){r[i]=ii-(c-r[c]);continue;}//r[ii]超出r[c]
               else j=r[ii];//r[ii]压线
            }
            //↓需要暴力扩散:i不在回文串中或r[ii]压线
            while(i+j<ss.size()&&i-j>=0&&ss[i+j]==ss[i-j])j++;
            r[i]=j;
            if(r[i]>=r[c])c=i;//更新c保证其全局最大
        }
        //↓生成答案
        string ans="",ANS=ss.substr(c-r[c]+1,2*r[c]-1);
        for(int i=0;i<ANS.size();i++)if(ANS[i]!='#')ans.push_back(ANS[i]);
        return ans;
    }
};

标签:--------,字符,05,Manacher,复杂度,C++,ii,字符串,回文
From: https://blog.csdn.net/dakingffo/article/details/140292822

相关文章

  • c++ protobuf安装记录
    googleprotobuf是一个灵活的、高效的用于序列化数据的协议。相比较XML和JSON格式,protobuf更小、更快、更便捷。googleprotobuf是跨语言的,并且自带了一个编译器(protoc),只需要用它进行编译,可以编译成Java、python、C++、C#、Go等代码,然后就可以直接使用,不需要再写其他代码,自带有......
  • 【C++修行之道】string类练习题
    目录387.字符串中的第一个唯一字符125.验证回文串 917.仅仅反转字母415.字符串相加(重点)541.反转字符串II387.字符串中的第一个唯一字符字符串中的第一个唯一字符-力扣(LeetCode)给定一个字符串 s ,找到它的第一个不重复的字符,并返回它的索引 。如果不存......
  • 【C++知识点总结全系列 (08)】:面向对象编程OOP
    这里写目录标题1、OOP概述(1)面向对象四大特征A.抽象B.封装C.继承D.多态(2)构造函数A.What(什么是构造函数)B.Why(构造函数的作用)C.Which(有哪些构造函数)(3)析构函数A.What(什么是析构函数)B.Why(析构函数的作用)(4)=default和=deleteA.WhyB.How2、继承(1)What(什么是继......
  • C++ AI异构搜索
    GitHub-facebookresearch/faiss:Alibraryforefficientsimilaritysearchandclusteringofdensevectors.#include<faiss/utils/simdlib.h>#include<cstddef>#include<cstdint>#include<memory>#include<random>#include......
  • C++反射的实现方式
    在C++中,反射(reflection)通常是指在运行时检查或修改程序结构的能力,比如类型、对象、方法、属性等。与许多动态语言(如Python、JavaScript)不同,C++是一种静态类型的编译语言,缺乏内置的反射机制。不过,我们可以使用一些技巧和库来实现类似反射的功能。 1.使用RTTI(运行时类型信息)......
  • 【ROS2】中级-编写动作服务器和客户端(C++)
    目标:用C++实现一个动作服务器和客户端。教程级别:中级 时间:15分钟 目录 背景 先决条件 任务1.创建custom_action_cpp包2.编写动作服务器3.编写动作客户端 摘要 相关内容 背景动作是ROS中异步通信的一种形式。动作客户端向动作服务器发送目标请求。动作......
  • C++中各类常用算法的总结以及使用
    1.常用算法文章目录1.常用算法1.常用遍历算法1.for_each2.transform2.常用查找算法1.find2.find_if3.adjacent_find4.binary_search5.count6.count_if3.常用排序算法1.sort2.random_shuffle3.merge4.reverse4.常用拷贝和替换算法1.copy2.replace3.repla......
  • 【C++】14.多态
    一、多态的概念在编程与现实的映射中就是,不同的对象完成相同的行为而产生的不同状态。举个栗子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。再举个栗子:动物的叫声,猫的叫声是“喵喵”;狗的叫声是“汪汪”;老虎的叫声是“劳资蜀道山......
  • NOIP2005 普及:第三题 采药
    辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你......
  • 【NOI】C++算法设计入门之贪心
    文章目录前言一、概念1.导入2.概念2.1贪心算法的核心思想2.2贪心算法的步骤2.3贪心算法的应用场景二、例题讲解问题:1372.活动选择问题:1456.淘淘捡西瓜问题:1551-任务调度问题:1561.买木头三、总结五、感谢前言贪心算法,如同成语"得陇望蜀"所描述的那样,总是......