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

示例 1:

输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:

输入: “cbbd”
输出: “bb”

思路

我们用一个 boolean dp[l][r] 表示字符串从 i 到 j 这段是否为回文。试想如果 dp[l][r]=true,我们要判断 dp[l-1][r+1] 是否为回文。只需要判断字符串在(l-1)和(r+1)两个位置是否为相同的字符,是不是减少了很多重复计算。


```java
public static String longestPalindrome2(String s) {
   
        int len = s.length();
        if(len <= 1) return s;
        //字符串为空或者长度等于1,直接返回
        boolean[][] dp = new boolean[len][len];
        //记录每一个子串的状态,dp[i][j]=true表明,以i为起点,j为终点的子串是回文
        int max = 0;
        //最大回文长度
        String ret = s.substring(0, 1);
        //存放回文,初始化为s的第一个字符,假如该字符串没有回文,那就直接返回字符串的第一个字符
        for(int r = 1; r < len; r++){
   
            for(int l = 0; l <  r; l++){
   
                //这两个循环很关键,
                if(s.charAt(r) == s.charAt(l) && (r - l <= 2 || dp[l+1][r-1])){
   
                    dp[l][r] = true;
                    if(max < r - l + 1) {
   
                        max = r - l + 1;
                        //max值更新
                        ret = new String(s.substring(l, r + 1));
                        //存放新的回文
                    }
                }
            }
        }
        return ret;
    }


## 1 判断一个字符串是否是回文串(左右对称)

单个数字默认为是

    public class isPlalindrome {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入字符串:");
            String str = sc.nextLine();
            System.out.println(isPlalindrome(str));
        }
        public static boolean isPlalindrome(String str) {
            //将字符串转化为字符数组
            char[] array=str.toCharArray();
            int left=0,right=array.length-1;//记录数组的开始位置和结束位置
            while(left<right)//开始位置小于结束位置时,进行判断  两位置处于对称位置上
            {
                if(array[left++]!=array[right--])//如果两位值的字符不相同 则不对称
                    return false;//返回false
            }
            //所有位除奇数位时的中间位均对应 返回true
            return true;
        }
    }

## 2 暴力法

 

    public class Violent {
    
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入字符串:");
            String str = sc.nextLine();
            String output = longestPlalindrome(str);
            System.out.println(output);
        }
        /**************************
         * 求解字符串的最长回文串----暴力法 O(n^3)
         * 通过两层循环找出字符串的所有子串  对每一个子串进行判断
         * 将是回文串的子串储存  当有新的回文串时,比较记录中的回文串和当前回文串的长度
         * 用较长的串替换当前串  如果两串长度相同,保留旧的
         * PS:如果想保存所有的回文串 可以修改记录回文串的结构为String数组(链表、hash表都可以)
         * @param str 要求解的字符串
         * @return 返回字符串中的最长回文串
         */
        public static String longestPlalindrome(String original)
        {
            //非空判断
            if((original==null)||original.length()==0)
            {
                return null;
            }
            //将字符串转换为字符数组
            char[] oriArray=original.toCharArray();
            int first=0;
            int end=0;//当前字符串中回文串的始末位置 包含末位置
    
            for(int i=0;i<oriArray.length-1;i++)//两次循环 查找字符串的所有子串
            {
                for(int j=i;j<oriArray.length;j++)
                {
                    //判断子串是否为回文串
                    int left=i,right=j;//记录左侧右侧的位置
                    while(left<right)//左侧下标小于右侧下标时 比较未完成
                    {
                        if(oriArray[left]!=oriArray[right])
                            break;//如果出现对称位置不相等元素  则不是回文串跳出循环
                        //判断下一对称位置
                        left++;
                        right--;
    
                    }
                    if(left>=right)//是否比较完成 是字符串是否为回文串的判断条件
                    {
                        if(j-i>end-first)//查找到回文串 且长度大于当前存储的回文串长度
                        {
                            //替换当前回文串
                            first=i;
                            end=j;
                        }
                    }
                }
            }
            //查找结束  将数组转化为字符串返回
            return String.valueOf(oriArray, first, end+1);
        }
    }

## 3 动态规划

为了改进暴力法,我们首先观察如何避免在验证回文时进行不必要的重复计算。考虑 “ababa” 这个示例。如果我们已经知道 “bab” 是回文,那么很明显, “ababa” 一定是回文,因为它的左首字母和右尾字母是相同的。
 



 
动态规划
动态规划一般用于(1)求最大最小值(2)求可行不可行(3)求方案总数。如果题目是求所有可行的方案,则肯定是不能用DP来做的。

思考动态规划的第一点----最优子结构:
思考动态规划的第二点----子问题重叠:
思考动态规划的第三点----边界:
思考动态规划的第四点----子问题独立:
思考动态规划的第五点----做备忘录:
思考动态规划的第六点----时间分析:

动态规划不是算法,它是一种方法,它是在一件事情发生的过程中寻找最优值的方法,因此,我们需要对这件事情所发生的过程进行考虑。而通常我们从过程的最后一步开始考虑,而不是先考虑过程的开始。
小技巧
  
解题思路
动态规划的话需要二维数组,一维肯定是不够的, 因为最终返回的是字符串。肯定要知道起始的串和结束的串,然后
dp[i][j]字符串从i到j是否为回文串,如果是当前的长度是j-i+1
dp方程 s[i]==s[j] dp[i][j] = dp[i+1][j-1]
解析1
  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190916201338279.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTU2MzE2MQ==,size_16,color_FFFFFF,t_70)

       public static String longestPalindrome2(String s) {
        int len = s.length();
        if(len <= 1) return s;
        //字符串为空或者长度等于1,直接返回
        boolean[][] dp = new boolean[len][len];
        //记录每一个子串的状态,dp[i][j]=true表明,以i为起点,j为终点的子串是回文
        int max = 0;
        //最大回文长度
        String ret = s.substring(0, 1);
        //存放回文,初始化为s的第一个字符,假如该字符串没有回文,那就直接返回字符串的第一个字符
        for(int r = 1; r < len; r++){
            for(int l = 0; l <  r; l++){
                //这两个循环很关键,
                if(s.charAt(r) == s.charAt(l) && (r - l <= 2 || dp[l+1][r-1])){
                    dp[l][r] = true;
                    if(max < r - l + 1) {
                        max = r - l + 1;
                        //max值更新
                        ret = new String(s.substring(l, r + 1));
                        //存放新的回文
                    }
                }
            }
        }
        return ret;
    }


## 4 中心扩展算法

事实上,只需使用恒定的空间,我们就可以在 O(n^2)的时间内解决这个问题。我们观察到回文中心的两侧互为镜像。因此,回文可以从它的中心展开,并且只有 2n - 12n−1 个这样的中心。
你可能会问,为什么会是 2n - 12n−1 个,而不是 nn 个中心?原因在于所含字母数为偶数的回文的中心可以处于两字母之间(例如 \textrm{“abba”}“abba” 的中心在两个 \textrm{‘b’}‘b’ 之间)。
 
评论1
终于看懂了这个中心扩展算法是什么意思了。 先来解释一下为什么中心是2n-1而不是n 比如有字符串abcba,这时回文子串是abcda,中心是c;又有字符串adccda,这时回文子串是adccda,中心是cc。 由此可见中心点既有可能是一个字符,也有可能是两个字符,当中心为一个字符的时候有n个中心,当中心为两个字符的时候有n-1个中心,所以一共有2n-1个中心。 然后for循环开始从左到右遍历,为什么会有两次expandAroundCenter,一次是i和i本身,一次是i和i+1,这就是上面说到的一个中心与两个中心。 而后会去判断这两种情况下谁的回文子串最长,并标记出这个子串在原字符串中的定位,即start和end。
import java.util.Scanner;

    /*
     * @Author liuhaidong 
     * @Description  中心扩展算法
     * @Date 10:01 2019/9/16 0016
     **/
    public class ExpandAroundCenter {
    
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入字符串:");
            String str = sc.nextLine();
            String output = longestPalindrome(str);
            System.out.println(output);
        }
    
        /*
         * @Author liuhaidong
         * @Description 最长回文串
         * @Date 9:50 2019/9/16 0016
         **/
        public static String longestPalindrome(String s) {
            if (s == null || s.length() < 1)
            {
                return "";
            }
            int start = 0, end = 0;
            //子串在原字符串中的定位
            for (int i = 0; i < s.length(); i++) {
                int len1 = expandAroundCenter(s, i, i);
                //判断奇数个 abcba
                int len2 = expandAroundCenter(s, i, i + 1);
                //判断偶数个 abba
                int len = Math.max(len1, len2);
                //比较两个谁更长
                if (len > end - start) {
                    start = i - (len - 1) / 2;
                    end = i + len / 2;
                }
                //以上是不懂意思
            }
            return s.substring(start, end + 1);
        }
        /*
         * @Author liuhaidong
         * @Description 中心结点法
         * @Date 9:50 2019/9/16 0016
         **/
        private static int expandAroundCenter(String s, int left, int right) {
            int L = left, R = right;
            while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {
                L--;
                R++;
            }
            return R - L - 1;
        }
    }

## 5 Manacher

首先,Manacher算法提供了一种巧妙地办法,将长度为奇数的回文串和长度为偶数的回文串一起考虑,具体做法是,在原字符串的每个相邻两个字符中间插入一个分隔符,同时在首尾也要添加一个分隔符,分隔符的要求是不在原串中出现,一般情况下可以用#号。下面举一个例子:
 

Manacher算法用一个辅助数组Len[i]表示以字符T[i]为中心的最长回文字串的最右字符到T[i]的长度,比如以T[i]为中心的最长回文字串是T[l,r],那么Len[i]=r-i+1。



![在这里插入图片描述](https://img-blog.csdnimg.cn/20210109221901410.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTU2MzE2MQ==,size_16,color_FFFFFF,t_70)