LeetCode5-最长回文子串(1)

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

二、示例


示例 1:

输入: “babad”

输出: “bab”

注意: “aba” 也是一个有效答案。

示例 2:

输入: “cbbd”

输出: “bb”

三、分析


1、算法的由来

在求解最长回文子串的问题时,一般的思路是以当前字符为中心,向其左右两边扩展寻找回文,但是这种解法的时间复杂度是O(N^2),那么能不能将时间复杂度再降低一点?做到线性?马拉车算法就完美地解决了这个问题。

2、预处理

回文字符串以其长度来分,可以分为奇回文(其长度为奇数)、偶回文(其长度为偶数),一般情况下需要分两种情况来寻找回文,马拉车算法为了简化这一步,对原始字符串进行了处理,在每一个字符的左右两边都加上特殊字符(肯定不存在于原字符串中的字符),让字符串变成一个奇回文。例如:

原字符串:abba,长度为4

预处理后:#a#b#b#a#,长度为9

原字符串:aba,长度为3

预处理后:#a#b#a#,长度为7

3、计算最长回文子串长度

以字符串"cabbaf"为例,将预处理后的新字符串"#c#a#b#b#a#f#"变成一个字符数组arr,定义一个辅助数组int[] p,p的长度与arr等长,p[i]表示以arr[i]字符为中心的最长回文半径,p[i]=1表示只有arr[i]字符本身是回文子串。

i 0 1 2 3 4 5 6 7 8 9 10 11 12

arr[i] # c # a # b # b # a # f #

p[i] 1 2 1 2 1 2 5 2 1 2 1 2 1

我们来比对分下一下最长回文半径和原字符串之间的关系。在上面例子中,最长回文子串是"#a#b#b#a#“,它以arr[6]为中心,半径是5,其代表的原始字符串是"abba”,而"abba"的长度为4,可以通过5减去1得到,是字符串"cabbaf"中的最长回文子串,那么我们是不是可以得出最长回文半径和最长回文子串长度之间的关系?

让我们再多看几个例子,如"aba",转换后是"#a#b#a#",以字符’b’为中心的回文,半径是4,减1得到3,3是原字符串的最长回文子串长度。

再例如"effe",转换后是"#e#f#f#e#",以最中间的’#'为中心的回文,半径是5,减1得到4,4是原字符串的最长回文子串长度。

因此,最后我们得到最长回文半径和最长回文子串长度之间的关系:int maxLength = p[i]-1。maxLength表示最长回文子串长度。

4、计算最长回文子串起始索引

知道了最长回文子串的长度,我们还需要知道它的起始索引值,这样才能截取出完整的最长回文子串。

继续以第三步中的字符串"cabbaf"为例,p[6]=5,是最长半径,用6(i)减去最长半径5(p[i])得到1,而1恰好是最长回文子串"abba"的起始索引。

我们再来看一个奇回文的例子。例如"aba",转换后是"#a#b#a#",p[3]=4,最长半径是4,i为3,用i减去4得到-1,数组下标越界了。

在偶回文的情况下,可以满足i减最长半径,而奇回文却会下标越界,我们需要在转换后的字符串前面再加一个字符,解决下标越界的问题,不能是’#’,那就加个’$‘字符吧,但是加过一个字符后,字符串的长度不是奇数了,只能在尾部再加一个不会重复出现的字符,比如’@’,这样字符串的长度依旧是奇数了,满足前面第三部分的条件。

加多一个字符后,奇回文可以正常做减法了,偶回文呢?

i 0 1 2 3 4 5 6 7 8 9 10 11 12 13

arr[i] $ # c # a # b # b # a # f #

p[i] 1 2 1 2 1 2 5 2 1 2 1 2 1

在补上字符’$'后,p[7]=5,用i减去最长半径,7-5=2,而理想的结果应该是1,那就再除以2吧,这样就能得到1了。而奇回文"aba"在用i减去最长半径后得到的是0,除以2后还是0,可以完美解决下标越界的问题。

结论:最长回文子串的起始索引int index = (i - p[i])/2。

5、计算p数组

在第三步和第四步中我们都用到了一个关键对象p数组,存放的是最长回文子串半径,那么它是怎么来的呢?

还是以上面的例子配合着看,

i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

arr[i] $ # c # a # b # b # a # f # @

p[i] 1 2 1 2 1 2 5 2 1 2 1 2 1

设置两个变量id和mx,id是所有回文子串中,能延伸到最右端位置的那个回文子串的中心点位置,mx是该回文串能延伸到的最右端的位置。

当i等于7时,id等于7,p[id] = 5,在以位置7为中心的回文子串中,该回文子串的右边界是位置12。

当i等于12时,id等于12,p[id] = 2,在以位置12为中心的回文子串中,该回文子串的右边界是位置14。

由此我们可以得出回文子串右边界和其半径之间的关系:mx = p[id]+id。

在这里插入图片描述

因为回文字符串是中心对称的,知道中心点位置id,如果一个位置的回文子串以i为中心,并且包含在以id为中心的回文子串中,即mx > i,那么肯定会存在另外一个以j为中心回文子串,和以i为中心的回文子串相等且对称,即p[j] = p[i],而i和j是以id为中心对称,即i+j=2_id,如果知道了i的值,那么j = 2_id - i。

但是我们需要考虑另外一种情况,如果存在一个以i为中心的回文子串,依旧有mx > i,但是以i为中心的回文子串右边界超过了mx,在i到mx的这段回文子串中,与另一端对称的以j为中心的回文子串还是相等的,此时p[i] = mx - i,p[j] = [pi],至于右边界mx之外的子串,即以i为中心的回文子串超出的部分是否还是满足上述条件就需要遍历比较字符了。

因此,在mx > i的情况下,p[i] = Math.min(p[2*id - i], mx - i)。

另外如果i大于mx了,也即是边界mx后面的子串,依旧需要去比较字符计算。

四、代码


package com.leetcode.code;

/**

  • LeetCode5-最长回文子串

  • 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

  • 示例 1:

  • 输入: “babad”

  • 输出: “bab”

  • 注意: “aba” 也是一个有效答案。

  • 示例 2:

  • 输入: “cbbd”

  • 输出: “bb”

*/

public class LeetCode5 {

// 方法一:暴力破解

public static String longestPalindrome(String s) {

int len = s.length(), max = 0;

String palindrome = “”;

for (int i = 0; i < len; i++) {

for (int j = i + 1; j <= len; j++) {

String content = s.substring(i, j);

if (isPalindromic(content) && content.length() > max) {

palindrome = s.substring(i, j);

max = Math.max(max, palindrome.length());

}

}

}

return palindrome;

}

private static boolean isPalindromic(String content) {

int len = content.length();

for (int i = 0; i < len / 2; i++) {

if (content.charAt(i) != content.charAt(len - i - 1)) {

return false;

}

}

return true;

}

// 方法二:Manacher’s Algorithm 马拉车算法

public static String longestPalindrome2(String s) {

int len = s.length();

if (len < 2) {

return s;

}

// 第一步:预处理,将原字符串转换为新字符串

String t = “$”;

for (int i = 0; i < len; i++) {

t += “#” + s.charAt(i);

}

// 尾部再加上字符@,变为奇数长度字符串

t += “#@”;

// 第二步:计算数组p、起始索引、最长回文半径

int n = t.length();

// p数组

int[] p = new int[n];

int id = 0, pRightBoundary = 0; // 回文串右边界

// 最长回文子串的长度

int maxLength = -1;

// 最长回文子串的中心位置索引

int centerIndex = 0;

for (int j = 1; j < n - 1; j++) {

p[j] = pRightBoundary > j ? Math.min(p[2 * id - j], pRightBoundary - j) : 1;

// 向左右两边延伸,扩展右边界

while (t.charAt(j + p[j]) == t.charAt(j - p[j])) {

p[j]++;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

面试前的“练手”还是很重要的,所以开始面试之前一定要准备好啊,不然也是耽搁面试官和自己的时间。

我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。

面试题及解析总结

三年Java开发,刚从美团、京东、阿里面试归来,分享个人面经

大厂面试场景

三年Java开发,刚从美团、京东、阿里面试归来,分享个人面经

知识点总结

三年Java开发,刚从美团、京东、阿里面试归来,分享个人面经

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
也是耽搁面试官和自己的时间。

我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。

面试题及解析总结

[外链图片转存中…(img-sBcVbBS8-1713462882041)]

大厂面试场景

[外链图片转存中…(img-KZV6Mc3z-1713462882042)]

知识点总结

[外链图片转存中…(img-diPtFXaP-1713462882043)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 27
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,有三种方法可以解决LeetCode上的最长回文子串问题。 方法一是使用扩展中心法优化,即从左向右遍历字符串,找到连续相同字符组成的子串作为扩展中心,然后从该中心向左右扩展,找到最长的回文子串。这个方法的时间复杂度为O(n²)。\[1\] 方法二是直接循环字符串,判断子串是否是回文子串,然后得到最长回文子串。这个方法的时间复杂度为O(n³),效率较低。\[2\] 方法三是双层for循环遍历所有子串可能,然后再对比是否反向和正向是一样的。这个方法的时间复杂度也为O(n³),效率较低。\[3\] 综上所述,方法一是解决LeetCode最长回文子串问题的最优解法。 #### 引用[.reference_title] - *1* [LeetCode_5_最长回文子串](https://blog.csdn.net/qq_38975553/article/details/109222153)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Leetcode-最长回文子串](https://blog.csdn.net/duffon_ze/article/details/86691293)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [LeetCode 第5题:最长回文子串(Python3解法)](https://blog.csdn.net/weixin_43490422/article/details/126479629)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值