manacher——马拉车算法(图文详解)

news2024/11/28 19:31: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/626589.html

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

相关文章

ChatGPT让沟通更智能、更便捷

ChatGpt是最近引起强烈关注的一种技术&#xff0c;它可以实现聊天机器人&#xff0c;为使用者解决复杂的信息获取和学习任务。但他也不仅仅是一个聊天机器人&#xff0c;它是一种基于深度学习算法的自然语言生成模型&#xff0c;由OpenAI公司开发。它可以模拟人类的对话方式&am…

AICG - Stable Diffusion 学习思考踩坑实录(待续补充)

关于模型 如果模型中没有各种角度的脚和手&#xff0c;无论你再怎么费劲心思&#xff0c;AI 都画不出来&#xff0c;目前C 站也没有什么好脚的例子&#xff0c;正面脚背面脚&#xff0c;但是没有侧面脚&#xff0c;脚这块还是很欠缺&#xff0c;希望未来有大牛能训练出来美脚 …

Python头歌合集(题集附解)

目录 一、Python初识-基本语法 第1关&#xff1a;Hello Python! 第2关&#xff1a;我想看世界 第3关&#xff1a;学好Python 第4关&#xff1a;根据圆的半径计算周长和面积 第5关&#xff1a;货币转换 二、turtle简单绘图 第1关&#xff1a;英寸与厘米转换 第2关&#xff1…

【MySQL数据库 | 第十篇】DCL操作

目录 &#x1f914; 前言&#xff1a; &#x1f914;DCL介绍&#xff1a; &#x1f914;1.DCL管理用户&#xff1a; 1.查询用户&#xff1a; 图示&#xff1a; 2.创建用户 示例1&#xff1a; 运行结果&#xff1a;​ 示例2&#xff1a; 运行结果&#xff1a;​ 3.修改…

基于html+css的图展示116

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

色环电阻出现的年代以及如何快速记忆计算

上次群里面大家兴趣盎然地讨论着几颗开关电源上面的色环电阻的读数。因为开关电源上面比较特殊&#xff0c;会出现几颗阻值很小的电阻&#xff08;小于1欧姆的&#xff09;。大家又非常感兴趣地重温了色环的计算方法。 色环的口诀我总结的是&#xff1a;黑&#xff0c;棕红橙&…

插件化技术

插件化技术 一.概述二.原理三.好处四.插件化涉及到的技术以及常用的插件化框架五.详细说明1.第一个问题&#xff1a;类加载&#xff08;1&#xff09;Android 项目中&#xff0c;动态加载技术按照加载的可执行文件的不同大致可以分为两种&#xff1a;&#xff08;2&#xff09;…

本地安装 Stable Diffusion 教程 Mac 版本

前面两篇讲了如何用免费的网络资源搭建 Stable Diffusion&#xff0c;有朋友问&#xff0c;有没有在本地搭建的教程。 以 MacBook Pro 为例&#xff0c;下面是安装步骤。 前置要求&#xff1a;Homebrew&#xff0c;Python 3.0。 如未安装Homebrew&#xff0c;请按照https://bre…

CASAIM与北京大学达成科研合作,基于3D打印技术加快力学性能试验分析,实现高效的力学结构设计和力学测试

近期&#xff0c;CASAIM与北京大学达成科研合作&#xff0c;基于3D打印技术进行力学性能试验分析&#xff0c;快速制造各种力学测试样件&#xff0c;从而实现高效的力学结构设计和力学测试。 北京大学是我国教育部直属的全国重点大学&#xff0c;位列国家“双一流”A类 、“985…

SpringBoot的宠物医院管理系统(有文档)

SpringBoot的宠物医院管理系统 本项目适合用来学习&#xff0c;以及二次开发&#xff0c;分享下 简介 1.访问地址 http://localhost:8080/ 超级管理员账户 账户名&#xff1a;admin 密码&#xff1a;admin123 宠物医生 账户名&#xff1a; laozhang 密码&#xff1a;12345…

异常检测学习笔记 二、基于角度和深度的极值分析技术

一、异常检测的概率模型 为您的数据选择合适的模型,选择一个概率阈值,低于该阈值将数据标记为异常,计算观察数据中每个实例的概率,低于阈值的情况属于异常情况。 研究表明,世界杯比赛的进球数可以很好地近似于泊松分布。在一场比赛中进n球的概率由下式给出: ,其中λ是每…

IP地址与MAC地址

引言&#xff08;有基础的同学可以不看&#xff09;&#xff1a;在复杂的网络通信中&#xff0c;有茫茫多的数据在中传输&#xff0c;它们是如何在相隔一步一步寻找到对方的呢&#xff1f; 网络通信的基本结构https://blog.csdn.net/qq_68140277/article/details/130937717?sp…

OpenStack部署(五)

OpenStack部署 11. 启动一个实例11.1 获取凭证11.2 创建虚拟网络11.3 创建主机规格11.4 生产环境的规格推荐11.5 生成一个键值对11.6 增加安全组规则11.7 创建块设备存储11.8 创建实例 12. 资源整理12.1 用到的端口12.2 openstack各组件常用命令1. openstack命令2. nova的常用命…

chatgpt赋能python:Python怎么5个一行?——提高代码可读性的方法

Python怎么5个一行&#xff1f;——提高代码可读性的方法 在Python编程中&#xff0c;提高代码可读性是非常重要的。然而&#xff0c;如果代码缩进不当&#xff0c;代码块就会非常难以辨认。那么&#xff0c;如何在不影响代码可读性的情况下使代码更清晰易懂呢&#xff1f;本文…

javaScript蓝桥杯---一起会议吧

目录 一、介绍二、准备三、目标四、代码五、完成 一、介绍 网络会议已经成为当下最流行的会议模式&#xff0c;为网络会议提供支持的当然是一些优秀的会议软件。 本题需要在已提供的基础项目中使用 Vue 2.x 知识完善代码&#xff0c;最终实现网络会议软件中&#xff0c;参会人…

javaScript蓝桥杯----权限管理

目录 一、介绍二、准备三、目标四、代码五、知识点六、完成 一、介绍 你有没有想过&#xff0c;在我们日常浏览的网页中&#xff0c;那些新闻或者商品内容是如何被输入到数据库中的呢&#xff1f;大家虽然没有用过&#xff0c;但是肯定听过“后台管理系统”&#xff0c;运营人…

从零开始的软路由之爱快虚拟机搭建openwrt

缘起 上篇文章我们介绍了爱快软路由的搭建方法&#xff0c;成功了实现了软路由的初级布置——能上网了。接下来就是搭建双软路由中的另一个openwrt了&#xff0c;上期介绍了爱快的特点&#xff0c;主要是用来多拨&#xff0c;分流&#xff0c;流控等操作&#xff0c;在这些方面…

maven 插件 assembly 打tar.gz包

maven 插件 assembly 打tar.gz包 一、项目目录二、pom文件1. profiles2. plugins3. resource 三、assembly.xml四、application.yml五、启动脚本1. start.sh2. stop.sh 六、执行 mvn 打包命令七、tar.gz 包上传服务器并解压八、执行 start.sh 启动脚本九、访问 swagger GitHub:…

Tomcat的部署及优化(贼详细)

目录 一、Tomcat服务器简介 1、Tomcat服务器 2、Tomcat三大核心组件 3、 Java Servlet 4、JSP全称Java Server Pages 5、 Tomcat 功能组件结构 6、 Container 结构分析 7、Tomcat 请求过程 二&#xff1a;Tomcat部署与安装 1.关闭防火墙&#xff0c;上传所需软件包 2.安…

跨部门沟通与协作迟迟进展不下去,如何有效解决问题?

在一个完整的项目中&#xff0c;多个专业技能版块的联动是必不可少的。然而&#xff0c;由于各个部门之间工作交集的存在&#xff0c;跨部门沟通与协作成为了必经之路&#xff0c;需要我们各部门凝聚力量&#xff0c;携手闯关。 但是&#xff0c;在工作中总会出现各种问题&…