manacher(图文详解)

news2024/12/20 2:15:37

文章目录

  • 简要介绍
  • 实际应用
  • 算法详解

简要介绍

 马拉车算法,Manacher‘s Algorithm 是用来查找一个字符串的最长回文子串的线性方法,是一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性O(N)

实际应用

  1. 刷题——最长回文子串,回文子字符串的个数。
  2. 生物中的基因排列可能会用到,DNA/RNA遗传信息。

算法详解

  • 本质:利用已有经验不断迭代

首先我们在求一个字符串的回文子串时,会有一个问题,长度为奇数的和长度为偶数的字符串求法不一

  1. 奇数长度——比如:aaa
    在这里插入图片描述
  2. 偶数长度——比如:bb
    在这里插入图片描述
    有些朋友到这可能会问了,中心有啥用?
    肯定有用了,下面我们就来说,有啥用。

假如给你一个字符串,一步一步找最长回文子串,你会怎么找?

青铜级选手:列出所有的子串,然后判断是否是回文子串,如果是,则计算子串长度,并跟当前的最长回文子串进行比较,如果比之长,则更新。
首先我们需要一层循环,确定长度,再需要一层循环,取遍当前确定长度的所有子串,还需要一层循环确定是否是回文字符串。——O(N3)
那么C语言写出的代码是这样的:

char * longestPalindrome(char * s)
{
    int len_s = strlen(s);
    int max_len = 0;
    int left_str = 0;
    int right_str = -1;
    for(int len = 0; len < len_s; len++)
    {
        int left = 0;
        int right = len;
        while(right < len_s)
        {
            //判断是否是回文子字符串
            int begin = left;
            int end = right;
            int is_true = 1;
            while(begin<=end)
            {
                if(s[begin]!=s[end])
                {
                    is_true = 0;
                    break;
                }
                begin++;
                end--;
            }
            if(is_true)
            {
                left_str = left;
                right_str = right;
                break;
            }
            left++;
            right++;
        } 
    }
    //开辟数组存储子串
    char * str = (char*)malloc(sizeof(char)*(len_s+1));
    memset(str,0,sizeof(char)*(len_s+1));
    for(int left = left_str,i = 0; left <= right_str; left++,i++)
    {
        str[i] = s[left];
    }
    return str;
}
  • 虽然能过,但是这是优化了一点,你试着把两个break去掉,就不能过了
  • 暴力求解中心下标可没有用!

黄金级选手:如果是回文子串,那么其中心下标,向两边进行扩展,不还是回文子串吗?这里需要注意的是偶数的中心下标有两种情况,那我们就假设字符串的每一个元素都是中心下标,遍历,然后需要注意第一步确定边界,往两边延伸一次可能是回文,往前面延伸一次也可能是回文。因此我们都要试一次,确定一个边界,再进行两边延伸。总结一下:中心下标一次循环,确定起始边界(两种可能性),然后向两边走,不嵌套的两次循环,因此时间复杂度为——O(N2)。
图解:
在这里插入图片描述

因此根据这种思路写出的C代码是这样的:

char * longestPalindrome(char * s)
{
    int ce_sut = 0;//center_subscript
    int len = strlen(s);
    int left_str = 0;
    int right_str = 1;
    int max_len = 0;
    for(ce_sut = 0; ce_sut < len; ce_sut++)
    {
        //确定起始边界
        if(s[ce_sut] == s[ce_sut+1])
        {
            int begin = ce_sut;
            int end = ce_sut + 1;
            while(begin>=0&&end<len\
            &&s[begin] == s[end])
            {
                begin--;
                end++;
            }
            //越界或者不是回文字符串
            begin++;
            end --;
            int len_cur = end - begin + 1;
            if(len_cur>max_len)
            {
                max_len = len_cur;
                left_str = begin;
                right_str = end+1;
            }
        }

        if(ce_sut-1>=0&&ce_sut+1<len&&\
        s[ce_sut-1]== s[ce_sut + 1])
        {
            //确定起始边界

            int begin = ce_sut-1;
            int end = ce_sut + 1;
            while(begin>=0&&end<len\
            &&s[begin] == s[end])
            {
                begin--;
                end++;
            }
            //越界或者不是回文字符串
            begin++;
            end --;

            int len_cur = end - begin + 1;
            if(len_cur>max_len)
            {
                max_len = len_cur;
                left_str = begin;
                right_str = end+1;
            }
        }
    }
    //开辟空间拷贝字符串
    char * str = (char*)malloc(sizeof(char)*(len+1));
    memset(str,0,sizeof(char)*(len+1));
    for(int left = left_str,i = 0; left < right_str; left++,i++)
    {
        str[i] = s[left];
    }
    return str;
}

大师级选手:利用manacher——马拉车算法 + 小技巧。

  1. 小技巧:n个字符,有n+1个空隙,因此可以插入n+1个相同的字符,总共2*n+1个字符,这样是不是就是不管是偶数还是奇数长度的字符,最后都变为了奇数个长度的字符。由于处理过后前后的字符并不相等,因此只管往两边扩!更细一步:为了防止出现越界的情况,我们需要再在开头和结尾放置两个与之都不同两个字符。
  2. 马拉车算法,前几种只是为了为此铺垫,如果弄清则对现在的理解会有一定的帮助。
    我们以一个字符串为例进行介绍
    处理之前: wxvxv
    处理之后:$#w#x#v#x#v#!
     因为既然是往两边扩的话我们也可以只扩右边(对称的性质),只需算一下右边扩的时候与中心坐标对称的的另一个下标的字符是否相等即可。比如中心下标为2,那么如果向右扩到了3,其对称下标为2*2 - 3等于1,那我们设中心下标为Mid,则左边的下标为Left,右边的下标为Right,则关系式就为—————— Right+Left = 2*Mid,则我们可以看这时的回文区间就是【Left,Right】闭区间。
     如果这里搞懂了,下面就更容易理解了,我们用这个例子一步一步走。

在这里插入图片描述
如果体验过这一个例子,离成功只剩一步之遥了!——总结思路。

第一步:求当前的中心坐标的长度,并保存此长度
第二步: 计算范围[Left,Right]。
第三步:从中心下标+1开始,到Right-1结束,判断当前坐标的对称坐标回文长度,是否等于当前坐标的回文长度——看对称坐标范围是否到边界,如果等于,就继续走,如果不等于就更新中心下标到当前坐标。
第四步:计算出处理后最大回文子串的长度,由处理前的最大回文子串的长度/2即可。因为处理后的最大回文长度必定是奇数,我们举a,处理之后为#a#,处理后的最大回文子串长度为3,/2等于1,再举一个例子,#a#a#,处理之后最大回文子串的长度为5,/2等于2,就等于原来的回文的字串的长度,要说为什么因为是/2,是相0取整的!

因此根据这种思路写出的C代码是这样的。

char * longestPalindrome(char * s)
{
    //处理字符串
    //开头和结尾需要加上都不同的两个字符,处理越界,这里我加上$和!
    //中间隔开的字符我们用#——字符串的长度+1
    //不要忘了还要为\0开辟空间
    //带上原来的字符串的长度
    //总计:n + n + 1 + 2 + 1 == 2*n + 4 
    int len_s = strlen(s);
    char* str = (char*)malloc(sizeof(char)*(2*len_s+4));
    memset(str,0,sizeof(char)*(2*len_s+4));
    //开头的下标为:0
    //结尾的下标为:2*len_s + 2
    str[0] = '$';
    str[2*len_s + 2] = '!';
    printf("%c",str[0]);
    //奇数放'#'
    //偶数放s的字符
    for(int i = 0;i < len_s; i++)
    {
        str[2*i + 1] = '#';
        str[2*i + 2] = s[i];
        printf("%c%c",str[2*i+1],str[2*i+2]);
    }
    //a这里只会处理成$#a!倒数第二个字符没有处理因此我们需要还要加上
    str[2*len_s + 1] = '#';
    printf("%c%c\n",str[2*len_s + 1],str[2*len_s + 2]);
    //manacher思路
    //记录中心下标回文字符串长度的数组
    int * infor = (int*)malloc(sizeof(int)*(2*len_s+2));
    memset(infor,0,sizeof(int)*(2*len_s+2));
    //记录最长回文子符串的长度
    int max_len = 0;
    //记录最长回文字符串的左右下标
    int left_str = 0;
    int right_str = 1;
    for(int mid = 1; mid < 2*len_s + 2;)
    {
        //先让中心下标往两边进行扩
        int left = mid;
        int right =  mid;
        int count = 0;
        while(str[left]==str[right])
        {
            left--;
            right++;
            count ++;
        }
        //这里不是回文字符串,但是我们往前再走一步就是回文字符串
        left++;
        right--;
        //我们需要保存一下最大回文半径的值
        infor[mid] = count;
        //优化
		if(right == 2*len_s + 1)
        {
            break;
        }
        //这里我们需要判断一下回文字符串的长度大于不大于当前最大的回文字符串的长度
        int len_cur = right - left + 1;//这里是左闭右闭
        if(len_cur > max_len)
        {
            max_len = len_cur;
            left_str = left;
            right_str = right+1;//左闭右开
        }
        //如果字符串的长度大于3就可能会有
        if(mid+1<right)
        {
            int k = mid + 1;
            while( k < right)
            {
            	//优化
            	//可能的最大半径
                int may_len = 2*len_s + 1 - k + 1;
                //当前的最大半径
                int cur_r = right - mid + 1;;
                if(may_len < cur_r)
                {
                    no_need = 1;
                    break;
                }
                //有两种情况
                //我们需要计算出k的对称下标的左边界与当前中心下标的左边界进行比较。
                int n = 2*mid - k;//求出中心下标对应的对称坐标
                int r = infor[n];//最大回文子串的半径
                int left_n = n - r + 1;//对称下标的左边界
                //如果对称下标的左边界小于等于中心下标的左边界,就不一定会有 
                if(left_n <= left)
                {
                    mid = k;
                    break;
                }
                else
                {
                    infor[k] = infor[n];
                }
                k++;
            }
        }
        else
        {
            mid++;
        }

    }
    //储存字符串
    char* str1 = (char*)malloc(sizeof(char)*(len_s+1));
    memset(str1,0,sizeof(char)*(len_s+1));
    int i = 0;
    for(int left = left_str; left < right_str; left++)
    {
        if(str[left]!='#')
        {
            str1[i++] = str[left]; 
        }
    }
    return str1;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/653683.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

玩转脚本实现自动化

目录 前言&#xff1a; 自动化原理 前置和后置脚本的区别&#xff08;它们的功能就如同名字一样&#xff09; 淘宝联盟接入文档 签名算法 调用示例 配置环境参数 设置全局变量 设置相关环境下的服务器地址 开始分析参数指定规则 添加接口 前言&#xff1a; 自动化测…

shell索引数组变量-定义获取拼接删除

目录 介绍数组的定义演示 数组的获取数组的拼接演示&#xff1a; 数组的删除 介绍 Shell 支持数组&#xff08;Array&#xff09;, 数组是若干数据的集合&#xff0c;其中的每一份数据都称为数组的元素。 &#xff08; 注意Bash Shell 只支持一维数组&#xff0c;不支持多维数组…

如何在ASP.NET Core应用中实现与第三方IoC/DI框架的整合?

我们知道整个ASP.NET Core建立在以ServiceCollection/ServiceProvider为核心的DI框架上&#xff0c;它甚至提供了扩展点使我们可以与第三方DI框架进行整合。对此比较了解的读者朋友应该很清楚&#xff0c;针对第三方DI框架的整合可以通过在定义Startup类型的ConfigureServices方…

大学毕业生自荐信范文

大学毕业生自荐信范文1 尊敬的贵公司领导&#xff1a; 您好&#xff01; 首先感谢您对我的关注&#xff0c;在我即将走出校门的时候&#xff0c;给我一次宝贵的工作机会。 学习了两年的会计专业相关知识后&#xff0c;已经使得我对会计行业有了深刻的认识。技能要靠经验&#x…

7.OpenCV-图像轮廓

1.通过OpenCV的findContours可以很方便的找到图片中内容的轮廓。 2.为了提取轮廓有更高的准确率&#xff0c;在提取轮廓前&#xff0c;需要对图片进行预处理&#xff08;二值阈值&#xff09;&#xff0c;只保留感兴趣的图像。 3.通过计算轮廓面积与外接矩形或外接圆的面积&a…

2023年6/16 操作系统小知识点

目录 分值第二章1. 进程状态转化的条件2. 进程控制块概念&#xff0c;作用&#xff0c;构成。3. 进程上下文概念&#xff0c;组成4. 进程的创建过程以及步骤5. 作业调度和低级调度算法&#xff0c;基本思想&#xff0c;7个&#xff0c;都要掌握6. 线程概念和处理过程7. 为什么有…

【玩转Docker小鲸鱼叭】虚拟化技术是什么?

到底什么是虚拟化&#xff1f; Docker 是一款基于容器虚拟化技术构建的软件&#xff0c;那到底什么虚拟化技术呢&#xff1f;在学习Docker之前&#xff0c;先简单了解下虚拟化技术。 虚拟化是云原生的实现基础&#xff0c;它能够帮助我们更加有效地利用物理计算机硬件。 虚拟…

德国Kipu Quantum与法国Pasqal合作优化量子算法

​ &#xff08;图片来源&#xff1a;网络&#xff09; 6月12日&#xff0c;德国量子软件公司Kipu Quantum宣布与法国中性原子量子计算领域的领导者PASQAL达成为期两年的研发协议&#xff0c;以优化量子算法功能&#xff0c;并为企业加速带来量子效益。 Kipu Quantum专注于为特…

Pytest教程__allure报告(11)

一、allure工具环境配置 windows安装allure 1、下载allure工具包 进入工具包官网&#xff1a;https://github.com/allure-framework/allure2/releases 2、解压下载包 3、将解压包的bin目录路径添加到环境变量中 4、打开cmd命令行输入 allure --version 确认是否安装成功 lin…

文件从 UNIX(LF) 批量改为 PC(CR+LF) ,编码格式保持源文件编码,CMD转换成功

如何把文件从 UNIX(LF) 批量改为 PC(CRLF) &#xff0c;编码格式保持源文件编码&#xff0c;通过电脑自带cmd 批量更改-1.0 chcp 65001 && FOR /F "tokens*" %f IN (dir /b D:\opt\output\DATA_FILE\20230531\*.DAT) DO type "D:\opt\output\DATA_FILE…

经典的设计模式21——策略模式

文章目录 策略模式 猛的发现策略模式和状态模式的结构图长得到好像&#xff0c;可得好好区分区分。 不过真的好像&#xff0c;就是方法那里传递的参数不一样。 百度来一波。 策略模式 定义&#xff1a; 定义了算法家族&#xff0c;分别封装起来&#xff0c;让他们之间可以互…

数字电路基础---锁存器

目录 锁存器 1、简介 2、实验任务 3、程序设计 3.1、缺少 else 分支的锁存器代码 3.2、补齐 else 分支 3.3、缺少 default 的 case 语句的锁存器代码 3.3、补齐 default 的 case 语句 4、本章总结 锁存器 锁存器&#xff08;俗称 Latch&#xff09;是数字电路中的一种…

别让存储成为ChatGPT大模型训练的关键瓶颈

当下科技行业be like... 据说现在的科技公司&#xff0c;不是在抢GPU&#xff0c;就是在往抢GPU的路上……此前4月&#xff0c;特斯拉CEO马斯克就购买了1万块GPU&#xff0c;他还称公司将继续大量购买英伟达的GPU。 在国内&#xff0c;近日也有报道称&#xff0c;字节跳动今年…

MySQL数据库事务和存储引擎(贼详细)

目录 一、MySQL 事物 1、事务的概念 2、 事务的ACID特点 &#xff08;1&#xff09;原子性 &#xff08;2&#xff09;一致性 &#xff08;3&#xff09; 隔离性 &#xff08;4&#xff09;事务之间的相互影响 &#xff08;5&#xff09; Mysql及事物隔离级别 &#…

你知道视频配音怎么配出好听的声音吗

小明&#xff1a;最近我在制作一个视频项目&#xff0c;但是我发现视频中的原声不够理想&#xff0c;我想给它配上好听的声音。你知道怎么配音才能让声音听起来更好吗&#xff1f; 小李&#xff1a;当然&#xff01;配音是提升视频质量的重要一环。想知道视频配音怎么配出好听…

uniapp实现应用wgt资源热更新

APP更新一般有两种形式 1、整包更新&#xff0c;通过hbuliderx提供的在线云打包就属于整包更新&#xff0c;属于全量更新&#xff0c;缺点就是打包时间长、要重新走市场审核。费时 2、wgt资源包热更新&#xff0c;通过hbuliderx打wgt包 &#xff0c;速度快&#xff0c;能在应用…

iphone测试中除了appium,还有更好用的工具吗

除了Appium&#xff0c;还有一些其他的工具可以用于iPhone测试&#xff0c;下面列举几个&#xff1a; 1. XCUITest XCUITest是苹果官方提供的UI自动化测试框架&#xff0c;可以用于iPhone和iPad应用程序的自动化测试。XCUITest可以模拟用户操作&#xff0c;例如点击、滑动、输…

机器学习实践(1.1)XGBoost分类任务

前言 XGBoost属于Boosting集成学习模型&#xff0c;由华盛顿大学陈天齐博士提出&#xff0c;因在机器学习挑战赛中大放异彩而被业界所熟知。相比越来越流行的深度神经网络&#xff0c;XGBoost能更好的处理表格数据&#xff0c;并具有更强的可解释性&#xff0c;还具有易于调参…

若依微服务 + seata1.5.2版本分布式事务(安装配置nacos+部署)

若依官方使用的1.4.0版本seata&#xff0c;版本较低配置相对更麻烦一些 一、seata服务端下载&#xff0c;下载方式介绍两种入口&#xff0c;如下&#xff1a; 1、找到对应版本&#xff0c;下载 binary 即可。 下载包名为&#xff1a;seata-server-1.5.2.zip 2. github上下载 …

WWDC2023 Metal swift 头显ARKit支持c c++ 开发

1 今年WWDC&#xff0c;我们看见了苹果的空间计算设备&#xff0c;visionOS也支持了c c API. 这有什么好处呢&#xff0c;不是说能够吸引更多c c开发者加入苹果开发者阵营&#xff0c;而是我们过去的很多软件&#xff0c;可以轻松对接到苹果的头显设备&#xff0c;让我们的软件…