相关链接:JavaScript算法学习之回文对匹配(一)暴力法
接着上篇,在上篇里我使用了暴力法来解决回文匹配的问题,尽管能够通过力扣的提交测试,但是太过粗暴,遇到大数据量的情况下效率不是很高。
所以我们肯定是要找一个聪明的办法的。思来想去,自己是想不出思路了,所以直接去看了官方的提供的方法,然后我用程序实现。就当练练编程,顺便学习一下新的思想和思维。
枚举前缀和后缀
假设有两个字符串s1、s2。
并且,s1 + s2 能够构成一个回文串。这时候,会有三种情况:
1)s1.length = s2.length
2)s1.length > s2.length
3)s1.length < s2.length
翻译成白话,就是s1、s2可能一样长,也可能长度不一样。
1)讨论情况一:s1、s2一样长
如果它们一样长(情况1),那么s1、s2就互为翻转。比如:
s1 : ‘abc’
s2 :‘cba’
它们其实是对称的。
2)讨论情况二、三:s1、s2不一样长
其实无论s1、s2谁长,情况都是一样的。我们这里就假设s1比s2长,比如:
s1 : ‘abcc’
s2 :‘ba’
此时,我们发现,s1中有一部分是回文的,即’cc’这一部分。剩下的部分’ab’,则恰好与s2,即’ba’是对称的。
不仅如此,回文部分还应该恰好是s1的前缀或者后缀。
s1 : ‘abcc’ // 'cc’为后缀
s2 :‘ba’
s1 : ‘ccba’ // 'cc’为前缀
s2 :‘ab’
我们姑且把这回文部分称为回文前缀、回文后缀吧。
3)三种情况的统一
我们可以把情况一归结为情况二、三的一种特例,即s1去除掉回文前/后缀(其实是空字符串),余下部分(其实就是它自身)与s2也是对称的。
因此,我们可以把三种情况统一起来。如果s1、s2要组成回文串,那么长度更长的那一个,去除它自身的回文前/后缀后,剩余部分必然与另一个是对称的。
归纳成算法
我们将要操作的字符串命名为s。
根据上文我们发现的一些特征,要想知道在某个字符串集数组 [<String>] 里,有没有能够与s匹配成回文串的元素,我们可以这么操作:把s去除掉回文前缀(或后缀),看其剩余部分的对称是不是属于 [<String>] 的一个元素。归纳一下:
第一步:去除s的回文前缀(或后缀),得到剩余部分s-
第二步:计算s-的对称(即镜像)-s
第三步:判断-s是否在 [<String>] 里
在这里,我不关心s究竟是上文所说s1、s2中的哪一个。我只要把s当成一个普通的字符串处理即可。
举个例子,假设s = ‘abcc’, 那么它拥有回文后缀’cc’,我把这部分去除后,剩余部分是’ab’。‘ab’的对称是’ba’,所以能够与s匹配成新回文的字符串就是’ba’。此时,你可以把s理解为s1、s2较长的那一个。
注意:我们需要把’’(空字符串)也当成一种特殊的回文前/后缀处理。
上例中,‘abcc’除了’cc’这个回文后缀外,它还可以有’ ‘(空字符串)这种回文前缀(你当成后缀也可以),于是它的剩余部分就是’abcc’,即它自身。此时剩余部分的对称变成了’ccba’,可以发现:它们同样可以组成回文串!
马拉车算法和字典树
我们在上文提出的算法步骤,每一步都可以用暴力法来实现。例如第一步:
第一步:去除s的回文前缀(或后缀),得到剩余部分s-
我们可以继续像原来一样,用暴力法,遍历s的全部字符去找回文前缀或后缀。
第三步也是如此:
第三步:判断-s是否在 [<String>] 里
我们可以遍历 [<String>]里的所有元素,来判断-s是否是 [<String>]的一个元素。
但是呢,这样用暴力法解决,不就仍然原来一样粗暴了吗?不如对自己要求高一点,将优化进行到底。
对于第一步,我们可以使用一种名为 马拉车 (Manacher)算法 的方法。这是一种高效的寻找最大回文串的方法。
对于第二步,我们使用 字典树,它常用于字符串文本的检索、统计。
之后,我将分别用两篇文章介绍它们,然后再用一篇文章来将它们组合起来,用来解决我们的问题。
(未完待续)