Manacher算法

news2025/2/27 20:48:22

0、概括

  • Manacher算法用于求解字符串中最长回文子串问题。

  • Manacher算法的核心:

    1. 理解回文半径数组;
    2. 理解所有中心的回文最右边界 R,和取得 R 时的中心点 C;
    3. 理解 L...(i')...C...(i)...R 的结构,以及根据 i ′ i' i 回文长度进行的状况划分
    4. 每一种情况划分,都可以加速求解 i i i 回文半径的过程
  • 补充:子串一定是连续的,如 abc123321def,最长回文子串就是 123321

  • 实际可用于DNA序列的研究。

1、引入

假设字符串 str 长度为 N N N,想返回最长回文子串的长度,且要求时间复杂度为 O ( N ) O(N) O(N)

2、暴力解找最长回文子串

【方法一】

直接以每个位置的字符作为中心点往两侧扩,比较左右两侧的字符是否相等,如果相等继续比较两侧的下一个字符,但是这种方法对于偶数长度的回文子串不适用。

【方法二】

那怎么办呢?处理原始字符串:最左边添加一个 #,每两个字符之间都添加一个 #,最后一个字符后也添加#

如原始字符串为 121aaaa232aa,处理后的字符串变为 #1#2#1#a#a#a#a#2#3#2#a#a#

好处:将处理后的字符串的每个字符位置作为中心往两侧扩,得到的最大长度除以 2,就能得到原始串的一个回文长度。

问:如果将原始串处理成新串的时候不用字符 #,而是用一个原始串中已经出现过的字符,会不会形成干扰?

答:不会,因为以任何位置为中心往两边扩的时候,都是实际的字符在比较,新加的字符在比较,不会存在实际字符和新加字符比较的情况,所以这个新加的字符是什么都可以。

时间复杂度:以 aaaaa 字符串为例,处理后的字符串 #a#a#a#a#a#,以每个位置为中心往两边扩需要比较的次数依次为:0 + 1 + 2 + 3 + 4 + 5 + 4 + 3 + 2 + 1 + 0,以字符串的中间位置为轴,左右两边都是等差数列,所以总的时间复杂度是 O ( n 2 ) O(n^2) O(n2)

之所以暴力解的过程慢,是因为以每个位置为中心往两侧扩的操作都是独立的,也就是之前的操作对于之后的操作没有任何指导意义。

3、Manacher算法找最长回文子串

Manacher算法在一个长度为 N N N 的字符串中找到最长回文子串的时间复杂度为 O ( N ) O(N) O(N)

基础概念

  • 回文直径 和 回文半径

abc12321def:回文串为“12321”,回文直径为5,回文半径为3

1221: 回文串为“1221”,回文直径为4,回文半径为2

  • 回文半径数组

记录从左往右以每个位置为中心向左右两边扩的长度

  • 最右回文边界

整型变量,用 R 表示,初始时R = -1。

图解R:
请添加图片描述请添加图片描述

不管是以哪个位置为中心扩的,只要让这个回文区域的右边界变得更右了,R就记录这个更往右的位置。

  • 最右回文边界的中心

C C C 变量表示,记录是哪个中心点使得R往右扩的。如上图解中,C依次为0、1、2、3、5,R不更新C就不会更新,R一旦更新,C一定会跟着更新。

流程

i i i 位置为中心向左右两边扩,可能会遇到的情况:

  1. i i i 没有被 R R R 罩住( i i i > R R R),则不优化,直接暴力扩;
  2. i i i R R R 罩住( i < = R i<=R i<=R),可以优化。这时候 C C C i i i 左边, R R R i i i 右边,以 C C C 为中心点,一定能找到 i ′ i' i,以及 R R R 的对称点 L L L,如果 i i i R R R 罩住一定存在这种拓扑关系:
    在这里插入图片描述
    然后根据 i ′ i' i 能扩多大的区域的信息来分类:
    • case1 : i ′ i' i 扩出的区域在 ( L , R ) (L,R) (L,R)
      请添加图片描述
      请添加图片描述
    • case 2 i ′ i' i的回文区域在 [ L , R ] [L,R] [L,R] 之外
      请添加图片描述请添加图片描述
    • case3 i ′ i' i为中心的的回文区域左边界和 L L L 压线请添加图片描述

整理各种情况,可得伪代码:

1)i > R, 无优化,暴力扩
2)i <= R:
	① i’扩充的区域在(L, R) 内,直接得到答案和i'的回文区域一样,不用扩,O(1)
	② i'扩充的区域左边界 < L,右边界 < R, 即不完全在(L,R)范围内,也不用扩,回文半径为 i 到 R 的距离,也是直接得到答案,O(1)
	③ i'扩充的区域左边界 = L,右边界 < R, i 到 R 这段为半径的区域不需要验证,再往右的位置对应左边继续往下验

复杂度分析:任何一个位置,都可能会失败一次(遇到不匹配的或没有字符了),失败的总次数 N N N 次;如果匹配成功,一定会使得 R R R 变大,2) 的 ①② 本来就是 O ( 1 ) O(1) O(1) 复杂度,不需要再讨论;而 2) 的 ③ 情况 R R R 内部不验, R R R 外部继续往右的时候,如果失败了就1次,如果成功依然会使得 R R R 变大。而 R R R 的变化有极限( 0 0 0~ N N N),且整个方法中,只要匹配成功, R R R 就增大, R R R 是不回退的,所以整个方法的时间复杂度 O ( N ) O(N) O(N)

4、Manacher算法代码实现

public class Manacher {
	//返回最长回文子串的长度
	public static int manacher(String s) {
		if (s == null || s.length() == 0) {
			return 0;
		}
		//1. 处理字符串,原始字符串处理为manacher字符串,即给前后和字符间的位置添加#
		//例:"12132" -> "#1#2#1#3#2#"
		char[] str = manacherString(s); 
		
		// 回文半径数组,该数组中的最大值/2就是结果
		int[] pArr = new int[str.length];
		
		int C = -1;
		// 讲解时:R代表最右的扩成功的位置
		// coding:最右的扩成功位置的,再下一个位置,即失败的位置
		int R = -1;
		int max = Integer.MIN_VALUE;
		
		//2. 以每个位置为中心点进行往左右扩的操作
		for (int i = 0; i < str.length; i++) { // 0 1 2
			// R第一个违规的位置,i<R 时,i才在R(扩成功的区域,即讲解时的R)内部;而i>=R时,i在R(扩成功的区域,即讲解时的R)外部
			// 如果i在R外(i>=R) ,以i为中心的回文半径至少是1(它自己);
			// 如果i在R内(i<R),就要用之前讨论的三种情况细分(根据i’的扩的区域):
			// 1)i’扩的区域在(L,R)内,i扩的区域 = i‘扩的区域
			// 2)i'扩的区域在[L,R]外,i扩的区域:i到R的距离为回文半径
			// 3)i'扩的区域左边界在L,i扩的区域:至少i到R的距离不用验
			// 下面这句代码就可以完成:
			// 2 * C - i 就是i',R - i 是i到R的距离,意思就是i‘的回文半径长度和R到i的距离,谁小就是至少不用验的区域
			// 这句代码的结果就是哪个区域不用验,也就是i位置扩出来的答案,i位置扩的区域,至少是多大。
			pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
			
			//不用验的区域的左右两侧的字符进行比较,就是往外扩的过程
			//上述的三种情况中,不用验的两种情况1)和2)在第一次进入这个循环就会break
			while (i + pArr[i] < str.length && i - pArr[i] > -1) {
				if (str[i + pArr[i]] == str[i - pArr[i]])
					pArr[i]++;
				else {
					break;
				}
			}
			
			//更新R和C的值
			if (i + pArr[i] > R) {
				R = i + pArr[i];
				C = i;
			}
			//max最后记录的是最大回文半径,而且是处理后的字符串的回文半径
			//如121,处理后#1#2#1#,回文半径为4,4-1=3就是原字符串的最大回文串长度
			//偶回文同样1221,处理后#1#2#2#1#,回文半径为5,  5-1=4就是原字符串的最大回文长度
			max = Math.max(max, pArr[i]); 
		}
		return max - 1;
	}

	public static char[] manacherString(String str) {
		char[] charArr = str.toCharArray();
		char[] res = new char[str.length() * 2 + 1];
		int index = 0;
		for (int i = 0; i != res.length; i++) {
			res[i] = (i & 1) == 0 ? '#' : charArr[index++];
		}
		return res;
	}

	// for test
	public static int right(String s) {
		if (s == null || s.length() == 0) {
			return 0;
		}
		char[] str = manacherString(s);
		int max = 0;
		for (int i = 0; i < str.length; i++) {
			int L = i - 1;
			int R = i + 1;
			while (L >= 0 && R < str.length && str[L] == str[R]) {
				L--;
				R++;
			}
			max = Math.max(max, R - L - 1);
		}
		return max / 2;
	}

	// for test
	public static String getRandomString(int possibilities, int size) {
		char[] ans = new char[(int) (Math.random() * size) + 1];
		for (int i = 0; i < ans.length; i++) {
			ans[i] = (char) ((int) (Math.random() * possibilities) + 'a');
		}
		return String.valueOf(ans);
	}

	public static void main(String[] args) {
		int possibilities = 5;
		int strSize = 20;
		int testTimes = 5000000;
		System.out.println("test begin");
		for (int i = 0; i < testTimes; i++) {
			String str = getRandomString(possibilities, strSize);
			if (manacher(str) != right(str)) {
				System.out.println("Oops!");
			}
		}
		System.out.println("test finish");
	}
}

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

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

相关文章

C-RNN-GAN:具有对抗训练的连续循环神经网络2016--生成音乐

C-RNN-GAN: Continuous recurrent neural networks with adversarial training 2016 Abstract 生成对抗网络已被提出作为一种有效训练深度生成神经网络的方法。我们提出了一种生成对抗模型&#xff0c;它适用于连续的序列数据&#xff0c;并通过在古典音乐集合上训练它来应用它…

计算机毕设Python+Vue校园学生体温管理系统(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

相见恨晚,Git这些功能太好用了

程序员宝藏库&#xff1a;https://gitee.com/sharetech_lee/CS-Books-Store 作为一名开发者&#xff0c;想必绝大多数同学都无法绕开Git。 作为一款工具&#xff0c;我认为它和word、powerpoint、Excel这些办公工具一样。 对于一部分同学&#xff0c;会一些基本的用法&#x…

【经验帖】项目经理的核心价值:以目标为导向做正确的事

项目经理小李的年终汇报心路历程&#xff08;心情犹如坐过山车&#xff0c;起起落落最后一蹶不振。&#xff09; 汇报前&#xff1a; 终于到年终汇报的日子了&#xff0c;毕竟我负责的项目任务从来没有延期过&#xff0c;都是按时完成&#xff0c;这次肯定得加薪了&#xff01…

[附源码]Node.js计算机毕业设计公租房管理系统Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

module命名空间

为什么要有namespaced命名空间&#xff1f; 默认情况下&#xff0c;模块内部的action、mutation和getter都是在全局命名空间。 假设两个modules内部有同名的action、mutation和getter&#xff0c;则vuex会报错。 namespaced作用&#xff1a;保证模块内部的高封闭性&#xff0c;…

021 | 阴离子诱导的系列双核镝配合物的合成及磁性质 | 大学生创新训练项目申请书 | 极致技术工厂

研究目的 近十几年来&#xff0c;随着科技的飞速发展&#xff0c;单分子磁体材料涉及的应用领域越来越宽广。众所周知&#xff0c;单分子磁体材料作为信息存储的基础对信息产业的发展具有一定的意义。此外&#xff0c;单分子磁体在超高密度储存、自旋电子器件、量子计算机等领域…

旅游住宿网站

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 网站前台&#xff1a;网站介绍、帮助信息、旅游资讯。景点信息、酒店信息 管理员功能&#xff1a; 1、管理网站介绍、帮…

day22【代码随想录】在每个树行中找最大值、填充每个节点的下一个右侧节点指针、二叉树的最大深度、二叉树的最小深度

文章目录前言一、在每个树行中找最大值&#xff08;力扣515&#xff09;二、填充每个节点的下一个右侧节点指针&#xff08;力扣116&#xff09;三、二叉树的最大深度&#xff08;力扣104&#xff09;1、非递归求解2、递归求解四、 二叉树的最小深度&#xff08;力扣111&#x…

【前端开发学习】4.JavaScript

文章目录1 JavaScript1.1 代码位置1.2 存在形式1.3 注释1.4 变量1.5 字符串类型案例&#xff1a;走马灯1.5 数组案例&#xff1a;动态数据1.6 对象&#xff08;字典&#xff09;案例&#xff1a;动态表格1.7 条件语句1.8 函数2 DOM2.1 事件绑定1 JavaScript 一门编程语言&…

第二证券|主力加仓电子、电气设备等行业 北向资金连续2日净流入

沪深两市股市涨跌互现&#xff0c;上证指数早盘震动下探&#xff0c;午后一度回升&#xff0c;尾盘再度回落&#xff1b;深证成指早盘探底回升&#xff0c;午后震动上扬&#xff0c;尾盘有所回落&#xff1b;创业板指早盘窄幅震动&#xff0c;午后震动上扬&#xff1b;科创50指…

D. Say No to Palindromes(构造 + 前缀和)

Problem - 1555D - Codeforces 如果这个字符串不包含一个长度至少为2的子串&#xff0c;我们就称它为美丽的字符串&#xff0c;这是一个回文。回顾一下&#xff0c;回文是指从第一个字符到最后一个字符以及从最后一个字符到第一个字符的读法相同的字符串。例如&#xff0c;字符…

京东物流 × StarRocks : 打造服务分析一体化平台Udata

作者&#xff1a;张栋&#xff0c;京东物流集团数据专家 京东集团 2007 年开始自建物流&#xff0c;2017 年 4 月正式成立京东物流集团&#xff0c;2021 年 5 月&#xff0c;京东物流于香港联交所主板上市。京东物流是中国领先的技术驱动的供应链解决方案及物流服务商&#xff…

Linux内核

内核属于操作系统的核心部分&#xff0c;它具有操作系统基本的功能&#xff0c;主要负责管理系统的内存、进程、设备驱动程序、文件系统和网络接口&#xff0c;因此&#xff0c;操作系统的性能和稳定性由内核决定。 1、内存管理 进程对内存的使用 计算机中所有要执行的程序都必…

大厂10年经验,我对Java高并发问题方案的总结,堪称教科书级

作为一个 Java 开发人员&#xff0c;多线程是一个逃不掉的话题&#xff0c;不管是工作还是面试&#xff0c;但理解起来比较模糊难懂&#xff0c;因为多线程程序在跑起来的时候比较难于观察和跟踪。 搞懂多线程并发知识&#xff0c;可以在面试的时候和周围人拉开差距&#xff0…

流媒体:依托于声网的连麦解决方案

一、背景 近些年&#xff0c;直播连麦这把火在流媒体领域整整燃烧了 6 年。从刚开始的简单探索&#xff0c;到现在的成熟全链路方案&#xff0c;不得不说日益增长的激烈竞争&#xff0c;已将让原本的蓝海领域变成了深海互搏。在这样的大环境下&#xff0c;是否意味着小厂将再也…

分布式系统(P2P Lookup)

文章目录P2P 系统NapsterBitTorrentGnutellaChordConsistent HashingSimple Key LocationScalable Key LocationKademliaRouting TableKademlia’s RPCAdaptabilityDistributed Hash TableP2P 系统 Peer to peer 系统&#xff1a; 每个结点在连接上是互联的&#xff0c;在功能…

Spring MVC: 一种简洁且强大的Web应用框架

⭐️前言⭐️ 这篇文章介绍Spring MVC&#xff0c;Spring MVC是现在基本所有Java程序的主流开发框架&#xff0c;这篇文章主要介绍三部分内容&#xff1a; 实现用户和程序的映射(在浏览器输入URL地址之后&#xff0c;能够在程序中匹配到相应方法)。服务器端得到用户的请求参数…

c语言笔记1 输入和输出注意事项,常量 变量 static

输出&#xff1a;printf c的编译器不会检测格式串中转换说明的数量和数据类型是否和后面的变量一致。转换说明的数据类型与实际数据类型不一致时&#xff0c;产生无意义的值。 int 类型的变量length&#xff0c;值为9&#xff0c;输出时将类型写为float或double&#xff0c;输…

【自然语言处理】【ChatGPT系列】InstructGPT:遵循人类反馈指令来训练语言模型

InstructGPT&#xff1a;遵循人类反馈指令来训练语言模型《Training language models to follow instructions with human feedback》论文地址&#xff1a;https://arxiv.org/pdf/2203.02155.pdf 相关博客 【自然语言处理】【ChatGPT系列】InstructGPT&#xff1a;遵循人类反馈…