Leetcode(5) - 最长回文子串 - java版 -全解

Leetcode(5) - 最长回文子串 - java版 -全解

题目

难度: 中等

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

示例1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例2:

输入: "cbbd"
输出: "bb"

一.暴力方法

此方法超出时间限制无法通过,不做推荐

思路:

选出所有子字符串可能的开始和结束位置,并检验它是不是回文。

实现:

class Solution {
    public String longestPalindrome(String s) {
        if(s.equals("")) return s;
        int n = s.length(),start = 0,maxlen = 1;
        for(int i =0;i<n;i++)
            for(int j =i+1;j<n;j++){
               int temp1 = i,temp2 = j;
                while(temp1 <temp2 && s.charAt(temp1) == s.charAt(temp2)){
                    temp1++; temp2--;
                }
                
                if(temp1 >= temp2 && j-i+1 >maxlen){
                    maxlen = j-i+1;
                    start = i;
                }
            }
        return s.substring(start,start+maxlen);
    }
}

二.最长公共子串法

此方法虽简单,但依旧超时,故仍不做推荐

思路:

先给出一个有缺陷的方案,

反转 S,使之变成 S′。找到 SS′之间最长的公共子串,这也必然是最长的回文子串。

该方案的缺陷在于: ,当 S 的其他部分中存在非回文子串的反向副本时,最长公共子串法就会失败

如: S = “abacdfgdcaba” , S′ = “abacdgfdcaba”

S以及 S' 之间的最长公共子串为 “abacd”。显然,这不是回文。

解决此缺陷的方法也很简单, 每当我们找到最长的公共子串的候选项时,都需要检查子串的索引是否与反向子串的原始索引相同。

实现:

class Solution {
 public  String longestPalindrome(String s) {
        if(s.equals("")) return s;
        int n = s.length();
        String s2 = new StringBuilder(s).reverse().toString(),ans = s.substring(s.length()-1);
        for(int i =0; i<n;i++)
            for (int j = i + 1; j < n; j++) {
                String sub = s.substring(i, j + 1);
                if (sub.length() > ans.length() && s2.contains(sub) && s.indexOf(sub) == s.indexOf(new StringBuilder(sub).reverse().toString()))
                    ans = sub;
            }
        return ans;
    }

}

三.动态规划

具体思路:

利用回文串的特性:

如果一个字串是回文字串,那么去掉左右两边的字符之后依然是回文。也可以说是一个回文字串,左右两边加上相同的字符,也是回文字串。

所以我们可以使用索引 ij 来表示一个字符串从索引 ij 的子串,

首先建立一个数组boolean[][] db

dp[i][j]表示索引i到j的子串是否是回文
dp[i][j] = true表示是回文,反之则为false

db的取值为: s.charAt(i) == s.charAt(j)&&j-i<2 || db[i+1][j-1]

长的子串dp[i][j]依赖于短的子串dp[i + 1][j - 1],所以由短到长依次计算

1.先计算一个字符,全为true
2.再计算两个字符,如果两个字符一样则为true
3.然后计算大于三个字符,直到整个字符串

实现:

class Solution {
    public String longestPalindrome(String s) {
        if(s.equals(""))  return s;

        int len = s.length(),left = 0,right = 0;
        // db[i][j] 表示字符串区间 [i, j] 是否为回文串
        boolean[][] db = new boolean[len][len];
        // 注意,这里的遍历与平常我们对字符串的遍历不一样
        for(int j = 0;j<len;j++)
            for(int i =0;i<=j;i++){
                db[i][j] = (s.charAt(i) == s.charAt(j))&&(j-i<2 || db[i+1][j-1]);
                //如果是回文字符串,并且比之前的回文字符串要长,更新字符串长度,记录字符串
                if(db[i][j] && j-i>right-left){
                    left = i;
                    right = j;
                }
            }
        return s.substring(left,right+1);
    }
}

四.中心扩展算法

具体思路:

因为回文中心的两侧互为镜像,所以,我们可以从回文的中心去展开,并且,一共有2n-1个这样的中心

所含字母数为奇数的回文的中心处在某一个字符上,而所含字母数为偶数的回文的中心处在两数中间,

故两种情况都要考虑.

实现:

class Solution {
    public String longestPalindrome(String s) {
        if(s.equals(""))  return s;
        
        int start = 0,end = 0;
        for(int i =0;i<s.length();i++){
            int len1 = expandAroundCenter(s,i,i); //以一个点为中心向外扩展,直到不是回文为止
            int len2 = expandAroundCenter(s,i,i+1);// 以两个点为中心向外扩展,直到不是回文为止
            int len = Math.max(len1,len2); // //找出两个中的最大回文长度
            if(len >end-start+1){ //如果大于保存的最大回文长度,则计算并更新最大回文的位置
                start = i -(len-1)/2;
                end = i+len/2;
            }
        }
        return s.substring(start,end+1);
    }
    
    public int expandAroundCenter(String s,int i,int j){
        while(i>=0 && j<s.length() && s.charAt(i) == s.charAt(j)){
            i--;j++;//分别向左右拓展
        }
        return j-i-1;
    }
}

注意:

这里的start和end的代数式需要易错,书写时应注意.

五.Manacher算法

具体思路:

由于回文分为偶回文(比如 bccb)和奇回文(比如 bcacb),而在处理奇偶问题上会比较繁琐,所以这里我们使用一个技巧,具体做法是:在字符串首尾,及各字符间各插入一个字符(前提这个字符未出现在串里)。

举个例子:s="abbahopxpo",转换为s_new="#a#b#b#a#h#o#p#x#p#o#",如此,s 里起初有一个偶回文abba和一个奇回文opxpo,被转换为#a#b#b#a##o#p#x#p#o#,长度都转换成了奇数

定义一个辅助数组int p[],其中p[i]表示以 i 为中心的最长回文的半径,例如:

i 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
s_new[i] # a # b # b # a # h # o # p # x # p #
p[i] 1 2 1 2 5 2 1 2 1 2 1 2 1 2 1 4 1 2 1

p[i]数组有一个性质,p[i]-1就等于该回文串在原串s中的长度

证明:

在转换后的字符串str_new中,所有的回文串的长度都是奇数,那么对于以i为中心的最长回文串的长度为2*p[i]-1,其中又有Len[i]个分隔符,所以在原字符串中的长度就是p[i]-1,那么剩下的工作就是求p数组, 如下图:

img

设置两个变量,mx 和 id 。mx 代表以 id 为中心的最长回文的右边界,也就是mx = id + p[id]

假设我们现在求p[i],也就是以 i 为中心的最长回文半径,如果i < mx,如上图,那么:

if (i < mx)  
    p[i] = min(p[2 * id - i], mx - i);

2 * id - i为 i 关于 id 的对称点,即上图的 j 点,而p[j]表示以 j 为中心的最长回文半径,因此我们可以利用p[j]来加快查找。

集合实现:

class Solution {
    public String longestPalindrome(String s) {
        List<Character> s_new = new ArrayList<>();
        for (int i = 0; i < s.length(); i++) {
            s_new.add('#');
            s_new.add(s.charAt(i));
        }
        s_new.add('#');
        List<Integer> Len = new ArrayList<>();
        String sub = "";//最长回文子串
        int sub_midd = 0;//表示在i之前所得到的Len数组中的最大值所在位置
        int sub_side = 0;//表示以sub_midd为中心的最长回文子串的最右端在S_new中的位置
        Len.add(1);
        for (int i = 1; i < s_new.size(); i++) {
            if (i < sub_side) {//i < sub_side时,在Len[j]和sub_side - i中取最小值,省去了j的判断
                int j = 2 * sub_midd - i;
                if (j >= 2 * sub_midd - sub_side && Len.get(j) <= sub_side - i) {
                    Len.add(Len.get(j));
                } else Len.add(sub_side - i + 1);
            } else //i >= sub_side时,从头开始匹配
            Len.add(1);
            while ((i - Len.get(i) >= 0 && i + Len.get(i) < s_new.size()) && (s_new.get(i - Len.get(i)) == s_new.get(i + Len.get(i))))
                Len.set(i, Len.get(i) + 1);//s_new[i]两端开始扩展匹配,直到匹配失败时停止
            if (Len.get(i) >= Len.get(sub_midd)) {//匹配的新回文子串长度大于原有的长度
                sub_side = Len.get(i) + i - 1;
                sub_midd = i;
            }
        }
        sub = s.substring((2 * sub_midd - sub_side) / 2, sub_side / 2);//在s中找到最长回文子串的位置
        return sub;
    }
}

数组实现:

class Solution {
    public String longestPalindrome(String s) {
        // 拼接得到新串
        String s_new = "*";
        for(int j =0;j<s.length();j++){
            s_new += s.charAt(j);
            s_new += "*";
        }
        // mx 代表以 id 为中心的最长回文的右边界
        // sub_mid为最长回文的中心索引
        // sub_side为最长回文的右边
        int mx = 0,id = 0,sub_mid = 0,sub_side = 0;
        int[] p = new int[s_new.length()];
        for(int i =1;i<s_new.length();i++){
            p[i] = i<mx ? p[i] = Math.min(mx-i,p[2*id-i]) :1;
            while(i-p[i]>-1&& i+p[i] <s_new.length()&&s_new.charAt(i-p[i]) == s_new.charAt(i+p[i])){// 这里要注意数组越界的判断
                p[i]++;
            }
            // 我们每走一步 i,都要和 mx 比较,我们希望 mx 尽可能的远,这样才能更有机会执行 if (i < mx)这句代码,从而提高效率
            if(i+p[i] > mx){
                mx = i+p[i];
                id = i;
            }
            // 更新最大回文长度的中心索引以及边界索引
            if(p[i] >p[sub_mid]){
                sub_mid =i;
                sub_side = i+p[i]-1;
            }
        }
        return s.substring((2 * sub_mid - sub_side) / 2, sub_side / 2);
    }
}

注意: 上面分割字符串得到返回值的方式有很多种,应灵活处理.

参考文章

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