CSDN周赛第49期 - 也谈马拉车

news2024/11/24 0:23:10

前言

C站的周赛已经很久没有新题了,已考过的题目我差不多都写过题解,若再重复写类似的文章,反而会降低博文质量分,而想要换个角度,却又难以找到动笔的欲望。所以虽然比赛发生在五一假期之前,但直到现在五一都过去了,我还没有想好该写什么。

本期考题如下:

1、隧道逃生 - 28期考过,题解

2、小艺照镜子 - 7、31、34期考过

3、大整数替换数位 - 37期考过,题解

4、清理磁盘空间 - 23期考过,题解

可以看到,关于最长回文子串的第二题,如果算上完全重复的31期和34期,已经考了四次了,足见这个知识点是多么基础且重要。虽然使用常见的中心扩展法也可通过本题,但作为有更高追求的选手,一定还想寻找和理解更加优化的复杂度为 O(n) 的马拉车(Manacher's)算法。于是,借着这期周赛,我们一起来通俗地研究一下此算法。

题目描述:已知字符串str。 输出字符串str中最长回文串的长度。

要谈马拉车,我们不得不先理解中心扩展,因为马拉车算法实际上是在中心扩展法的基础上,通过使用静态数组( P 数组),用空间换时间,省去了重复的比较和计算。于是,我们先来看中心扩展法是如何解决最长回文子串问题的。

顾名思义,中心扩展,就是根据回文字符串左右字符通过中心完全对称相等的特性,以某一点为中心,左右指针从小到大依次向两边扩散,检查左右字符是否相等。一直到左右字符不相等的时候停下,得到的字符串长度就是最长的回文子串的长度。

第一个问题

当回文字符串的长度是偶数时,并没有一个确切的中心点,因为中间对称的是两个字符。

所以并不能从某个中心点扩展开来,而是从中心两个字符扩展开来。

为了在代码上解决这个问题,通常有两种做法:一是分别检查奇偶字符串。示例代码如下。注意:此代码只是为了和后面的马拉车算法保持一致,以方便理解,实际上中心扩展法的实现方式可以有多种不同形式。

str = input()
n = len(str)
res = 1 # 最短的回文字符串就是一个字符
for i in range(n-1):
    j = 0
    if str[i] == str[i+1]: # 当回文子串的长度是偶数时
        while i - j > 0 and i + j < n - 2 and str[i-j-1] == str[i+j+2]:
            j += 1
        res = max(res, j*2+2)
    else: # 当回文子串的长度是奇数时,也就是存在中心字符
        while i - j > 0 and i + j < n - 1 and str[i-j-1] == str[i+j+1]:
            j += 1
        res = max(res, j*2+1)
print(res)

第二种方法就是把原始字符串“改造”一下,在字符串的首、尾及每个字符的间隔各加入一个特殊字符,将其构造成一个无论何种情况都存在中心点的字符串。以特殊字符“#”为例,我们可以将字符串构造成如下图所示:

当回文串长度为奇数时,中心点是某个字符,当长度为偶数时,中心点是我们构造的特殊字符“#”。于是,我们在上面的代码稍作修改,同样的逻辑,代码量也随之减少:

str = '#' + '#'.join(input()) + '#' # 构造字符串
n = len(str)
res = 1
for i in range(n-1):
    j = 0
    while i - j > 0 and i + j < n - 1 and str[i-j-1] == str[i+j+1]:
        j += 1
    res = max(res, j) # 只需记录一半的长度 j,就是原始字符串中回文串的长度
print(res)

由于我们改造了原始字符串,所以实际回文子串的长度只有遍历中的一半,也就是 j ,这也是我们的代码可以简化的原因之一。

实际上,使用上述中心扩展法已经可以轻松 AC 本题了。让我们一起看看还能如何优化吧。

马拉车优化了什么?

可以看出,如果字符串的长度是 n,需要分别以 n 个字符为中心,进行扩展检查。而每次检查,都需要检查 0 (当中心为左右首字母时) 到 n/2 次,所以中心扩展法的渐进算法复杂度是 O(n^2)

但是,中心扩展法实际的计算次数并没有达到 n^2。比如在极端情况,当字符串中没有回文子串时,中心扩展法的时间复杂度实际上也是 O(n)。——想象一下上面的第二段代码,内层while循环由于不满足左右字符相等的条件,所以并不会执行。

那么优化的空间在哪儿?

我们来看另一个极端情况,假设字符串是由同一个字符组成,如果使用中心扩展法,实际需要计算多少次呢?

上图以长度为 5 的字符串为例,当检查完中间的 “a” 时,实际上就已经完成了所有字符的比较,所以,似乎、隐约觉得中间 “a” 之后的中心点是可以优化的。

我们再看一例,假设我们检查的是一个长度为7的回文字符串,如下图所示:

当检查完这个回文串的中心点“c”时,实际上右边的字符都已经被比较过了。从实际比较的次数也可以看出来,以“c”右边的字符为中心点进行查找和比较的次数,与左边的相同。也就是说,如果已经确定了一个大的回文串,那么在其中心点(center)的右边,右边界(right)的左边,以这些字符作为中心点所需要的扩展比较次数,应该有一部分已经在中心点(center)左边与其相对应的字符作为中心点时比较过了。

上面说的可能还是太复杂了,我们画图来说明。

假设已经通过中心扩展确定了一个较大的回文子串(从 left 到 right),也就是说从这个回文子串的中心点 center 到右边界 right 之间的字符已经被比较过一次。

继续循环,将 center 到 right 之间的位置为 i 的字符作为中心点进行扩展检查时,在 center 与 left 之间,必定存在一个 i 的镜像点 j ,——回文串都是对应的。

由于我们在检查 center 之前,必定已经检查过点 j,所以关于点 j 的回文子串的大小,有两种可能:

1. 以点 j 为中心扩展的回文子串大小不超过这个较大回文子串的左边界 left 。由于回文串的对称性,点 i 为中心进行扩展的回文子串必然与 j 相等。也就是说,从点 i 到点 k 之间的字符都可以不用再检查了。

2. 以点 j 为中心扩展的回文子串大于等于这个较大回文子串的左边界 left 。这种情况下,由于我们还没有比较过 right 之后的字符,所以不能确定以点 i 为中心进行扩展的回文子串是否会大于 j 。所以点 i 到 right 之间的字符也都不需要再检查,只需要扩展检查 right 之后的字符即可。

综上所述,马拉车算法优化的部分,其实就是通过借助回文字符串的对称性,优化了 center 到 right 之间位置的中心扩展部分。——因为其中一部分已经在 left 到 center 之间扩展比较过了。

如此,我们可以发现,从左往右进行中心扩展,已经比较过的字符,都不需要再进行比较了,也就是说,所有位置的字符都只需要检查一次,所以马拉车的算法复杂度在任何情况下都是 O(n)。——当然,如果仔细数的话,比较次数应该是 n 的常数倍。

实现

知道了马拉车的原理(大概),我们来捋一捋如何用代码实现。

1. 由于我们是从左向右依次选择中心点进行扩展比较,所以很显然,需要使用一个 for 循环,而 i 就代表了字符串的每个位置。

2. 我们还需要记录当前已经遍历过的 i 所发现的最大回文子串,也就是 center 和 right 的位置。由于对称性,我们不需要再记录 left 的位置,因为 left = 2*center -right 。同样地,点 j 的位置也可以通过计算得到:j = 2*center-i 。

3. 前面说过,我们要优化的部分是 center 到 right 之间的字符进行中心扩展的比较部分。所以我们需要比较 i 和 right 的关系:如果 i < right,说明存在优化的可能。

4. 最关键的,我们要记录左边点 j 为中心的回文子串的大小,从而决定了右边点 i 为中心时,可以省去多少比较位置。所以,我们引入了一个数组 P ,用来记录每个点为中心时,最大回文子串的半径。

将上面几点带入之前的代码,更新如下:

str = '#' + '#'.join(input()) + '#' # 构造字符串
n = len(str)
p = [0] * n # 初始化 P 数组
center = right = 0 # 初始化较大回文串
for i in range(n):
    if i < right:
        j = 2 * center - i # 通过中心计算对称的位置 j
        p[i] = min(right - i, p[j]) # 根据 j 为中心的回文串大小,有两种可能
    while i - p[i] > 0 and i + p[i] < n - 1 and str[i - p[i] - 1] == str[i + p[i] + 1]: # 在此基础上再进行扩展
        p[i] += 1
    if i + p[i] > right: # 如果目前为止最大回文串的半径大于i,说明存在优化的可能
        center, right = i, i + p[i] # 更新最大回文串的中心和右边界
print(max(p))

后记

这篇关于马拉车算法的博文只能算是粗略的分析了,如果需要完整学习,建议搜索查看网络上其他文章。但是如果已经看过其他文章但是却还是一知半解,但愿你会从这篇文章里get到一点启发。

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

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

相关文章

camunda升级事件的用途

在Camunda中&#xff0c;升级事件&#xff08;Escalation Event&#xff09;是一种可以在工作流中出现异常情况时触发相应操作的事件类型。使用升级事件可以帮助工作流更加灵活地处理异常情况&#xff0c;以确保工作流的正常运行。 使用升级事件可以处理以下情况&#xff1a; …

【Java EE 初阶】如何保证线程安全

目录 1.线程是什么&#xff1f; 2.线程安全&#xff08;重点&#xff09; 1.概念&#xff1a; 1.举例&#xff1a;用两个线程分别对同一个变量做五万次自增&#xff0c;观察答案是否符合预期 那么是哪些原因造成了这种线程不安全的现象呢&#xff1f;我们一起来分析一下&am…

搭建Plex媒体服务器,打造家庭多媒体中心【公网远程访问】

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频&#xff0c;已经算是生活中稀松平常的场景了&#xff0c;特别是各…

MAC安装MySQL

安装MySQL 登录官网dev.mysql.com/downloads/m… 下载社区版mysql&#xff0c;选择dmg格式的安装包。下载完成后&#xff0c;开始安装。 注意&#xff1a;选择Use Legacy Password Encryption。 解决无法启动MySQL问题 打开设置中的mysql图标&#xff0c;发现红点&#xff0…

Syslog-ng RHEL 的安装和配置

syslog-ng 作为 syslog 的替代工具&#xff0c;可以完全替代 syslog 的服务&#xff0c;并且通过定义规则&#xff0c;实现更好的过滤功能。 作为运维来说一个好的日志工具比什么都重要。 通常我们会管理不同的服务器&#xff0c;因此我们需要把日志集中一下以便于快速查找。…

GUI编程(二)

Swing Swing是GUI&#xff08;图形用户界面&#xff09;开发工具包。 早期的AWT&#xff08;抽象窗口工具包&#xff09;组件开发的图形用户界面&#xff0c;要依赖本地系统&#xff0c;当把AWT组件开发的应用程序移植到其他平台的系统上运行时&#xff0c;不能保证其外观风格…

贪心刷题~

1、洛谷P2240 【深基12.例1】部分背包问题 贪心策略&#xff1a;拿金币单价高的。 #include<iostream> #include<cstring> #include<algorithm> using namespace std;struct gold{int v;int m; } q[101];bool cmp(gold a,gold b){return a.v*b.m>b.v*a.m…

SpringCloud-微服务Eureka服务注册中心

微服务&服务注册中心 前言一、微服务1.什么是微服务2.单体架构和微服务架构2.1.单体架构2.2.微服务架构 二、服务注册中心1.服务注册中心简介2.Eureka服务注册中心2.1.Eureka Server开发2.2 Eureka Client开发 3.Eureka的自我保护机制3.1.Eureka自我保护机制简介3.2.Eureka…

MySQL数据库连接超时自动断开的解决方案

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

windows下msys2编译64位的ffmpeg源码

目前遇到过两次需求&#xff0c;需要编译ffmpeg源码。网上下载的编译好的源码里面可能不全&#xff0c;很多时候需要自行编译源码。本文介绍自行编译ffmpeg64位源码&#xff08;32位通过相似的方式为编译成功&#xff0c;不知道原因&#xff09; 环境&#xff1a; 2023.5.4下载…

团队密码管理器Passbolt的安装

老苏下载了吴恩达联手 OpenAI 推出的 Prompt for developer 课程&#xff0c;总长度大概在一个半小时左右&#xff0c;可以让我们学习正确的 ChatGPT Prompt 工程 虽然课程对话是英文&#xff0c;但有中文字幕&#xff0c;课程地址&#xff1a;https://www.aliyundrive.com/s/…

[Gitops--9]微服务项目sangomall代码配置修改及资源清单文件

微服务项目sangomall代码配置修改及资源清单文件 1. 中间件的地址 1.1 Nacos 集群外 nacos-server.intra.com 192.168.31.211集群内 nacos-server.sangomall.svc.cluster.local. nacos-server.sangomall.svc.cluster.local.:88481.2 Redis 集群内 redis.sangomall.svc.c…

ipad有必要用手写笔吗?电容笔和Apple pencil区别

与Apple Pencil最大的不同之处&#xff0c;在于普通的电容笔并不具备着重力压感&#xff0c;而是会给人一种倾斜的压感。如果不是频繁作画&#xff0c;那就用一支普通的电容笔。这种电容笔不但可以用于办公室&#xff0c;也可以用于记笔记、做练习。再说了&#xff0c;一支苹果…

深入理解 Linux 内核(二)

系列文章目录 Linux 内核设计与实现 深入理解 Linux 内核 深入理解 Linux 内核&#xff08;二&#xff09; Linux 设备驱动程序 Linux设备驱动开发详解 文章目录 系列文章目录五、定时测量1、时钟和定时器电路2、Linux 计时体系结构&#xff08;1&#xff09;计时体系机构的数据…

200G 400G光模块介绍

200G 光模块封装有2种&#xff0c;分别是QSFP56和QSFP-DD。 200G QSFP56有2种光模块&#xff0c;第一种是200G QSFP56 SR4&#xff0c;第二种是200G QSFP56 FR。 200G QSFP56 SR4的封装形式是QSFP56&#xff0c;速率是200G&#xff0c;波长是850nm&#xff0c;最远传输距离是10…

儿童书写台灯哪个牌子比较好?盘点护眼学生用台灯品牌排行

想要拥有一个健康的视力对于我们多么重要&#xff0c;日常生活多么不便利&#xff0c;就是像家里孩子考学时视力也是对于未来专业选择的一个阻碍。 想要孩子不吃近视的苦&#xff0c;从小就要开始抓孩子对于视力和眼睛的呵护。 养成好习惯必须保持一个正确的学习姿势&#xff…

redis 持久化 RDB + AOF

redis 持久化 RDB AOF 1.redis持久化----两种方式 RDB&#xff08;Redis DataBase&#xff09;和AOF&#xff08;Append Only File&#xff09; RDB&#xff0c;简而言之&#xff0c;就是在不同的时间点&#xff0c;将redis存储的数据生成快照并存储到磁盘等介质上 AOF&am…

视频剪辑学习 pr 中视频

2023年中视频学习计划&#xff0c;学习资料全套视频。全网一手资料&#xff0c;有意者V&#xff1a; 第一章- 基础知识 第二章- 素材获取 第三章- 文案创作及搬运改写 第四章- 智能配音与自己配音修音 第五章- 剪辑基础快速入门 手机剪映零基础快速入门 电脑剪映零基础快速入门…

360SEO 360搜索引擎算法的基础知识

360搜索引擎是中国的一家互联网搜索引擎公司&#xff0c;由奇虎360公司推出。作为中国互联网领域的知名品牌之一&#xff0c;它的搜索算法一直备受关注和研究。那360搜索引擎有哪些算法的基础知识呢&#xff1f; 一、概述 360搜索引擎算法是一个非常庞大、复杂的系统&#xff…

科研人的利器:利用New Bing五分钟读完一篇论文

大家好&#xff0c;我是可夫小子&#xff0c;关注AIGC、读书和自媒体。解锁更多ChatGPT、AI绘画玩法。加我&#xff0c;拉你进群。 New Bing『新必应』是微软一款集成了ChatGPT的搜索引擎&#xff0c;它以聊天的方式来进行信息搜索&#xff0c;这不同过去几十年通过对话框搜索信…