647. 回文子串

647. 回文子串_第1张图片
题目链接:https://leetcode-cn.com/problems/palindromic-substrings/description/


基于动态规划 - 1

这题基于动态规划的思想来解答的话,实现思想参考自 :http://www.bkjia.com/Cyy/988261.html

借助一个辅助二维数组 dp[i][j],其中 ij 一起作用,表示字符串 s 中下标从 ij 的子串的回文子串的个数,且对于矩阵 dp,只考虑包含对角线的右上部分(即 j >= i 的情况),因为 dp[i][j]dp[j][i] 在意义上是一样的(因此,考虑左下部分也是一样的,只是具体的代码逻辑也要做相应的调整)。

于是对于 dp[i][j] 就会有如下公式(特殊情况暂不考虑):

dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1];

子串 s.sub(i,j),可以看作是子串 s.sub(i+1,j-1) 和两个边缘字符 s(i)s(j) 的组合:

对于 dp[i+1][j],表示子串 s.sub(i+1,j-1)与左边的字符 s(i) 组合的新的字符串的回文子串个数,其值是以 dp[i+1][j-1] 为基础的;对于 dp[i][j-1] 同理。
但是,需要注意,因为 dp[i+1][j]dp[i][j-1] 都包含了一次 dp[i+1][j-1] 的值,重复了一次,因此需要减去一次。

根据上述公式,可以知道大范围的字符串的 dp 值(即 dp[i][j])需要根据小范围的字符串的 dp 值(dp[i+1][j]、dp[i][j-1]、dp[i+1][j-1])来计算,如果是以矩阵右上角部分去计算,则 dp 矩阵的计算顺序如下图:
647. 回文子串_第2张图片
画圈的为对应的 dp[i][j] 的值,折线表示计算的顺序。最终的结果即为 dp[0][s.length() - 1] 的值,即上图的 dp[0][4]

然后就是要考虑到特殊的情况了:
(1)当 j == i 时,即单个字符,dp[i][j] 为 1;
(2)当 j - i == 1 时,即子串长度为 2(s(i)s(j) 两两相邻),此时 dp[i][j] 至少为 2,而且当 s(i) == s(j) 时,就为 3。
(3)当 j - i > 1 时,此时如果 s(i) == s(j)s.sub(i+1,j-1) 为回文串,则还需要在上述公式的基础上 +1,因为此时子串 s.sub(i,j) 也是一个回文串,该情况也需要考虑到。(这里需要特别注意,不能仅仅是判断 s(i)s(j) 是否相等而不考虑 s.sub(i+1,j-1) 是否为回文串,因为 “s.sub(i+1,j-1) 为回文串“ 是前提条件,举例测试数据 "fdsklf"


public class PalindromicSubstrings {

    public static int countSubstrings(String s) {
        if (s == null) return 0;
        int len = s.length();

        int[][] dp = new int[len][len];
        for (int i = 0; i < len; i++) {
            dp[i][i] = 1;
        }
        //用一个 Map 保存判断过的是否回文的子串,提高效率
        Map map = new HashMap<>();

        for (int i = len - 2; i >= 0; i--) {
            for (int j = i + 1; j < len; j++) {
                if (j - i == 1) {
                    dp[i][j] = 2;
                    if (s.charAt(i)==s.charAt(j)) dp[i][j]++;
                } else {
                    dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1];
                    //注意 String 的 substring() 函数的结束边界值的特殊性
                    if (s.charAt(i) == s.charAt(j) && isPalindromic(s.substring(i + 1, j ),map)) {
                        dp[i][j] ++;
                    }
                }
            }
        }

        return dp[0][len - 1];
    }

    /**
     * 判断是否为回文字符串
     */
    private static boolean isPalindromic(String s, Map map) {
        if (map.containsKey(s)) return map.get(s);

        int t = s.length() /2;
        int p1 = t-1;
        int p2;

        if (s.length()%2==0) {
            p2 = t;
        } else {
            p2 = t+1;
        }

        while (p1>=0&&p2if (s.charAt(p1--)!=s.charAt(p2++)) {
                map.put(s, false);
                return false;
            }
        }

        map.put(s, true);
        return true;
    }

    public static void main(String[] args) {
//        System.out.println(countSubstrings("fdsklf"));
        System.out.println(countSubstrings("longtimenosee"));
    }
}

基于动态规划 - 2

参考自我朋友的想法

大致思想也与上面的类型,只不过这里辅助数组 map[i][j] 是用来判断从 ij 的子串是不是回文串

    public static int countSubstrings(String s) {
        int length = s.length();
        boolean[][] map = new boolean[length][length];

        //单个字符也算
        int num = length;

        // 单个字符
        for (int i = 0; i < length; i++) {
            map[i][i] = true;
        }

        //从第0个字符开始,两个两两相邻的字符组成的字符串
        //如果两个字符相等,则为回文串
        for (int j = 0; j + 1 < length; j++) {
            if (s.charAt(j + 1) == s.charAt(j)) {
                map[j][j + 1] = true;
                num++;
            }
        }

        //从第0个字符开始,三个及以上连续的字符组成的字符串
        //如果 s.sub(j,j+i) 除去两边的字符之后的子串(即 s.sub(j+1,j+i-1))为回文串且除去的两个字符相等(即 s(j)==s(j+1))
        for (int i = 2; i < length; i++) {
            for (int j = 0; j + i < length; j++) {
                if (map[j + 1][j + i - 1] && s.charAt(j + i) == s.charAt(j)) {
                    map[j][j + i] = true;
                    num++;
                }
            }
        }

        return num;
    }

基于遍历

参考自 https://leetcode-cn.com/submissions/detail/1031526/ 7ms 用时的范例:

    public static int countSubstrings2(String s) {
        int result = 0;
        if ( s == null){
            return result;
        }
        int length = s.length();
        for(int i =0; i < length;i++){
            result += count(s,i,i);//以 s(i) 为中心向两边扩散
            result += count(s,i,i+1); //以 s(i) 与 s(i+1) 为中心向两边扩散
        }
        return result;
    }
    public static int count(String s,int beg,int end){
        int count = 0;
        while( beg >=0 && end < s.length()&& s.charAt(beg) == s.charAt(end)){
            count++;
            beg--;
            end++;
        }
        return count;
    }

你可能感兴趣的:(LeetCode,渣渣的算法历练之路)