LeetCode 5. Longest Palindromic Substring(最长回文子串)

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example:
Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example:
Input: "cbbd"
Output: "bb"

大致意思就是给定一个字符串s,找到s中最长回文子串
回文的含义是:正着看和倒着看相同,如abba和abcba.
子串的含义是:在原串中连续出现的字符串片段。
这是一道经典的算法题,在《算法竞赛入门经典》以及2016腾讯实习生校招笔试题中都曾遇到过,之前的文章中也曾解决过一个更复杂的问题
解决该问题最方便的方法是中心扩展法,从中心向两边扩展回文串,需要注意的是,回文串有两种形式:长度为奇数的回文串(形如abcba)以及长度为偶数的回文串(形如abba).
使用这种方法寻找最长回文子串的时间复杂度为O(n​^2​​),空间复杂度为O(1)。
代码如下:

public class Solution {
    public String longestPalindrome(String s) {

        int n = s.length();
        int start = 0, maxLen = 0;
        for (int i = 0; i < n; i++) {   // 以i为中心向两边扩展
            for (int j = 0; i - j >= 0 && i + j < n; j++) { // 长度为奇数的回文串(形如abcba)
                if (s.charAt(i - j) != s.charAt(i + j)) {
                    break;
                }
                if (2 * j + 1 > maxLen) {
                    maxLen = 2 * j + 1;
                    start = i - j;
                }
            }
            for (int j = 0; i - j >= 0 && i + 1 + j < n; j++) { // 长度为偶数的回文串(形如abba),中心点(两个b)位置分别为i和i+1,向两边扩展范围(i-j,i+1+j)
                if (s.charAt(i - j) != s.charAt(i + 1 + j)) {
                    break;
                }
                if (2 * j + 2 > maxLen) {
                    maxLen = 2 * j + 2;
                    start = i - j;
                }
            }
        }
        return s.substring(start, start + maxLen);
    }
}

此外,解决该问题还可以使用Manacher算法(中文名马拉车算法),它可以在O(N)的时间复杂度内得到一个字符串中以每一个字符为中心的最长回文子串。而对于长度为偶数的回文串,比如abba,可以通过插入未出现过的特殊字符#,从而转化成长度为奇数的回文串#a#b#b#a#
该算法的基本原理就是利用已知回文串的左半部分来推导右半部分
用数组p[i]表示以i为中心的最长回文子串半径长度,从前向后扫描字符串s中的每一个字符计算出它们对应的p[i],找到最大的p[i]即找到了s的最长回文子串。
在从前向后扫描的过程中,需要计算p[i]时一定已经计算好了p[1]....p[i-1],假设现在扫描到了i+k这个位置,需要计算p[i+k]
定义max是以i+k之前的字符为中心的所有回文串所能延伸到的最远的位置(回文右边界),假设这个字符的位置是i,即max = i + p[i];// i为i+k之前到达最远的回文串中心
这时i+k的位置分布有两种情况:

  1. i+k这个位置不在前面的任何回文串内部(不含max点),即 i+k >= max,这时初始化p[i+k] = 0;//本身是回文串
    然后p[i+k]左右延伸,即while(s.charAt(i+k - p[i+k]) == s.charAt(i+k + p[i+k])) p[i+k]++;

  2. i+k这个位置被前面以i为中心的回文串包含,即 i+k < max,这样的话p[i+k]就不是从0开始了

对于第二种情况,根据回文串的性质,可知i+k这个位置的字符关于i与i-k对称,这时p[i+k]又要分为以下三种情况来计算(黑色是以i为中心的回文串范围,绿色是以i-k为中心的回文串范围)

2.1

如上图,若以i-k为中心的回文串有一部分在以i为中心的回文串范围之外(绿色的左端在黑色范围之外),则以i+k为中心的回文串范围一定是橙色部分,有 p[i+k] = max - (i+k) = i + p[i] - i - k = p[i] - k,即 p[i+k] = Math.min(p[i-k], p[i]-k);(p[i] - k < p[i-k])

  • 如果小于橙色部分,则违反了与i-k关于i的对称性

  • 假设超出橙色部分,如图中的紫色延长线c和d,根据以i为中心的回文子串性质,b部分与c部分相对于i对称;同理根据以i-k为中心的回文子串性质,b部分与a部分相对于i-k对称,最终得出a部分与d部分相对于i位置对称,超出了以i为中心的回文子串(黑色)范围,假设不成立。

2.2

如上图,若以i-k为中心的回文串全部在以i为中心的回文串范围内未达到左侧边界(左侧绿色在黑色范围之内),则以i+k为中心的回文串范围一定是右侧绿色部分,有p[i+k] = p[i-k],即 p[i+k] = Math.min(p[i-k], p[i]-k);(p[i-k] < p[i] - k)

  • 如果小于右侧绿色部分,同样是违反了与i-k关于i的对称性

  • 假设超出右侧绿色部分,如图中的紫色延长线c和d,根据以i为中心的回文子串性质,b部分与c部分相对于i对称,a部分与d部分也相对于i对称,最终得出a部分与b部分相对于i-k位置对称,超出了以i-k为中心的回文子串(左侧绿色)范围,假设不成立。

2.3

如上图,若以i-k为中心的回文串全部在以i为中心的回文串范围内恰好达到左侧边界(左侧绿色到达黑色范围左边界),则有 i-k - p[i-k] = i - p[i],即 p[i-k] = p[i] - k
以i+k为中心的回文串范围一定包含右侧绿色部分,并有可能超出(如图橙色部分),这时初始化 p[i+k] = Math.min(p[i-k], p[i]-k);(p[i-k] = p[i] - k),并向外延伸while(s.charAt(i+k - p[i+k]) == s.charAt(i+k + p[i+k])) p[i+k]++;

综合上面所有情况,总结出核心代码:

p[i+k] = 0;
if(i+k < max) {
    p[i+k] = Math.min(p[i-k], p[i]-k);
}  
while(s.charAt(i+k - p[i+k]) == s.charAt(i+k + p[i+k])) {
    p[i+k]++;
}

上面分析中把当前的字符定义在位置i+k,配合i及i-k这种对称的命名方式是为了便于我们理解,在实际编程中当前字符的位置通常为i,最长延伸回文串的中心点通常定义成id(id < i),则i关于id的对称位置为id - (i - id) = 2 * id - i,把上面的思想带入到实际编程中:

在遍历到位置为i的字符时,已知以位置为id的字符为中心的回文串已延伸到的最远位置max = id + p[id],如果当前字符在这个回文串中,我们就要赋值 p[i] = Math.min(p[2 * id - i], max - i),即以当前位置i关于id的对称位置2 * id - i为中心的回文串半径与该对称位置距离最长延展回文串左边界长度max - i二者间的较小值。代码如下:

public class Solution {
    public String longestPalindrome(String s) {

        String t = "#";
        for (int i = 0; i < s.length(); i++) { // 插入#,统一转化为长度为奇数的回文串
            t += s.charAt(i) + "#";
        }
        int[] p = new int[t.length()];       // p[i]表示以i为中心的最长回文子串半径长度
        int maxP = 0, maxC = 0;              // 数组p中的最大值,最长回文子串的中心点
        int id = 0;                          // 当前位置之前已到达最远的回文串中心
        int max = id + p[id];                // 当前查找位置之前,已知能影响最右边的串
        for (int i = 0; i < t.length(); i++) {
            if (i < max) {                   // 当前字符在之前最远回文串的范围之内
                p[i] = Math.min(p[2 * id - i], max - i);
            }
            while (i - p[i] >= 0 && i + p[i] < t.length() && t.charAt(i - p[i]) == t.charAt(i + p[i])) {
                p[i]++;
            }
            if (i + p[i] > max) {   // 更新最远回文串
                id = i;
                max = id + p[id];
            }
            if (p[i] > maxP) {      // 保留最大值信息
                maxP = p[i];
                maxC = i;
            }
        }
        if (t.charAt(maxC) == '#') { // 还原长度为偶数的回文串
            return s.substring((maxC - 2) / 2 - (maxP - 2) / 2, (maxC - 2) / 2 + 1 + (maxP - 2) / 2 + 1);
        } else {                     // 还原长度为奇数的回文串
            return s.substring((maxC - 1) / 2 - (maxP - 1) / 2, (maxC - 1) / 2 + (maxP - 1) / 2 + 1);
        }
    }
}

总体来说,Manacher算法还是比较复杂的,不会有人要求你在45分钟的coding session中去实现它,大致了解即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 最长回文子串——Manacher 算法 1. 问题定义 最长回文字符串问题:给定一个字符串,求它的最长回文子串长度...
    林大鹏阅读 2,737评论 0 6
  • 问题定义 最长回文子串问题:给定一个字符串,求它的最长回文子串长度。 解法1:暴力解法 找到字符串的所有子串,判断...
    HITMiner阅读 661评论 0 2
  • 最长回文串问题是一个经典的算法题。 0. 问题定义 最长回文子串问题:给定一个字符串,求它的最长回文子串长度。如果...
    曾会玩阅读 4,003评论 2 25
  • 上一篇KMP算法之后好几天都没有更新,今天介绍最长回文子串。 首先介绍一下什么叫回文串,就是正着读和倒着读的字符顺...
    zero_sr阅读 2,263评论 2 8
  • 这次要记录的是一个经典的字符串的题目,也是一个经典的马拉车算法的实践。相信在很多地方都会考到或者问到这道题目,这道...
    柠檬乌冬面阅读 2,900评论 0 9