最长回文子字符串

117 阅读2分钟

题目

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。 示例 2:

输入:s = "cbbd" 输出:"bb" 示例 3:

输入:s = "a" 输出:"a" 示例 4:

输入:s = "ac" 输出:"a"  

提示: 1 <= s.length <= 1000 s 仅由数字和英文字母(大写和/或小写)组成

动态规划

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;


public class Main {


    public static void main(String[] args) {

        Main main = new Main();
        main.longestPalindrome("abc");

    }



    // DP table(自底向上记录)
    Map<Integer, List<int[]>> dpTable = new HashMap<>();
    public String longestPalindrome(String s) {

        int length = s.length();

        // 从短字符串开始找起, 回文串长度为1, 当长度为2, 长度为3
        for (int i = 1; i <= 1000; i ++) {
            if (i > 2) {
                // 根据长度为1和长度为2的回文字符串得到
                int pre = i - 2;
                if (dpTable.containsKey(pre)) {
                    List<int []> tempList = dpTable.get(pre);
                    for (int [] index : tempList) {
                        int before = index[0] - 1;
                        int after = index[1];
                        if (before >= 0 && after < length && s.charAt(before) == s.charAt(after)) {
                            // 添加
                            addMap(before, after + 1, i);
                        }
                    }
                }
            } else {
                for (int j = 0; j <= s.length() - i; j ++) {
                    String temp = s.substring(j, i + j);
                    if (isPalindrome(temp)) {
                        addMap(j, i + j, i);
                    }
                }
            }

        }

        Set<Integer> set = dpTable.keySet();
        Object[] obj = set.toArray();
        Arrays.sort(obj);
        int maxKey = Integer.parseInt(obj[obj.length - 1].toString());
        int [] maxIndex = dpTable.get(maxKey).get(0);
         return s.substring(maxIndex[0], maxIndex[1]);


    }

    public boolean isPalindrome(String tempString) {
        if (tempString.length() == 1) {
            return Boolean.TRUE;
        }

        char [] charArray = tempString.toCharArray();

        for (int i = 0; i < (tempString.length() / 2); i ++) {
            if (tempString.charAt(i) != tempString.charAt(tempString.length() - 1 - i)) {
                return Boolean.FALSE;
            }
        }
        return Boolean.TRUE;

    }

    public void addMap(int start, int end, int key) {
        List<int[]> temp = new ArrayList<>();
        if (dpTable.containsKey(key)) {
            temp = dpTable.get(key);
        } else {
        }
        temp.add(new int[]{start, end});
        dpTable.put(key, temp);
    }



}

基本思路

  1. 首先根据题目分析, 是否是最优子结构, 能否找到状态转移方程, 发现当长度大于2当时候, 例如等于3的时候, 是通过对于长度为1的回文子字符串两头添加一个相同的字符, 就可以得到一个回文串, 长度为4的时候可以由长度为2的回文子字符串得到. 因此类推, 5由3得到, 6由4得到. 因此求解时, 只需要根据上一次的结果, 判断守首尾的两个字符即可, 同时如果没有3, 就肯定没5, 后续的判断非常简化.

  2. 因此假定回文字符串长度为1, 开始递增求取, 直到结束, 需要用Dp table记录不同长度回文子字符串的索引下标情况

  3. 状态转移方程: 1. dp(i -2)存在, 且在原字符串中索引下标为m, n, 然后s.charAt(m -1) == s.charAt(n + 1) 条件是 i > 2 dp(i) = 2. 直接判断长度为1, 长度为2的回文子字符串有哪些

  4. 需要假定回文串长度为多少的结束条件可以是题目要求, 也可以是原是字符串的长度

缺点

  1. 当前的dp table记录了所有长度的回文字符串的索引, 但是题目只需要最长的, 也就是说假如一个长度为2的回文字符串索引是(2, 3), 然后我在计算4的时候, 我判断(2, 3)存在, 同时判断索引1和4字符相同, 这个时候记录(1, 4)即可, 无需通过list来存储, 直接用二维数组即可

  2. 找最长的索引的时候, 方式也比较愚蠢

优化后的动态规划


public class Main {


    public static void main(String[] args) {

        Main main = new Main();
        main.longestPalindrome("cbbd");

    }


    public String longestPalindrome(String s) {

        int length = s.length();
        String result = "";
        boolean [][] dpTable = new boolean[length][length];
        // l为每次期望的回文子字符串的长度
        for (int l = 0; l < s.length(); l ++) {
            // i代表每个长度从索引0开始遍历
            int ss = 0;
            for (int i = 0; i < s.length() - l; i ++) {
                // i代表长度为l + 1子字符串的开始索引, j代表结束索引
                int  j = l + i;
                if (l == 0) {
                    // l为0, 实际长度为1的, 任意位置都是回文子字符串
                    dpTable[i][j] = Boolean.TRUE;
                } else if(l == 1) {
                    // l为1, 实际长度为2的, 只需满足i和j的字符相等即可
                    dpTable[i][j] = (s.charAt(i) == s.charAt(j));
                } else {
                    // 长度大于2的, 需要dp[i + 1][j - 1]为true
                    // 即长度差2, 索引各差1
                    dpTable[i][j] = (s.charAt(i) == s.charAt(j)) && dpTable[i + 1][j - 1];
                }
                // 如果得到更长的子字符串, 刷新结果即可, 无需存储旧有的字符串
                if (dpTable[i][j] && j - i + 1> result.length()) {
                    result = s.substring(i, j + 1);
                }

            }
        }
        return result;

    }

}

改进

  1. 同样是发现当长度大于2时的子回文字符串和l -2 的有关, 但是全程的判断都是通过字符串中的索引来完成, 更加高效

  2. dp table采用二维数组的方式来记录, 更加高效

  3. 将之前的判断一个回文子字符串两头的字符是否相同, 转化为判断当前的两个字符是否相同, 变成去掉当前两个字符, 所包含的字符串是否是回文子字符串, 本质一样, 但是这样判断省去了边界判断.

  4. 最终结果的存储变成了一旦有更长的就替代原来的, 无需存储太多字符串