dp 回文子串 & 回文子序列-leetcode-647. 回文子串 & leetcode-516. 最长回文子序列

647. 回文子串

题目描述

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

提示:

  • 1 <= s.length <= 1000
  • s 由小写英文字母组成

dp 回文子串 & 回文子序列-leetcode-647. 回文子串 & leetcode-516. 最长回文子序列_第1张图片

暴力

分别枚举 start、end、k,判断 每个子串是否为 回文串。

  • 时间复杂度: O ( n 3 ) O(n^3) O(n3)

dp 五步曲

  1. dp[i][j] 表示 s[i, j] (即,j >= i)是否为回文串(true:是回文串;false:不是)

  2. 递推公式
    仍然分为 2 种情况,即 s[i]和s[j]相等、s[i]和s[j]不相等:

    • s[i]和s[j] 不相等时,则 s[i, j] 一定不是 回文串,dp[i][j] = false;
    • s[i]和s[j] 相等时,分为 3 种情况:
      • 1)i == j,即 只有一个字符时,一定是回文串;
      • 2)i + 1 == j,即 有2个字符时,也是回文串;
      • 3)i + 1 < j,即 至少有3个字符时,则需要看 s[i+1, j-1] 是不是 回文串
     // 2、递推公式
    	if (s.charAt(i) == s.charAt(j)) {
    	    // 当前字符相等,分为 3 种情况:
    	    // (1)i == j,即 只有一个字符时,一定是回文串
    	    // (2)i + 1 == j,即 有2个字符时,也是回文串
    	    // (3)i + 1 < j,即 至少有3个字符时,则需要看 s[i+1, j-1] 是不是 回文串
    	    if (i == j || i + 1 == j) { // (1)、(2)
    	        dp[i][j] = true;
    	    } else { // (3)多于2个字符
    	        dp[i][j] = dp[i + 1][j - 1];
    	    }
    	} else { // 当前字符 不相等,则 s[i, j] 一定不是 回文串
    	    dp[i][j] = false;
    	}
    
  3. 初始化(dp[i][j] 全部为 false)

  4. 遍历顺序
    由递推公式可知,当前状态依赖其 左下方,所以需要 从下到上、从左到右

  5. 打印dp

class Solution {
    public int countSubstrings(String s) {
        int n = s.length();
        boolean[][] dp = new boolean [n][n]; // dp[i][j] 表示 s[i, j] (即,j >= i)是否为回文串(true:是回文串;false:不是)
        // 3、初始化(dp[i][j] 全部为 false)
        // 4、顺序(由递推公式可知,当前状态依赖其 左下方,所以需要 从下到上、从左到右)
        int res = 0;
        for (int i = n - 1; i >= 0; i--) { // 从下到上
            for (int j = i; j < n; j++) { // 从左到右
                // 2、递推公式
                if (s.charAt(i) == s.charAt(j)) {
                    // 当前字符相等,分为 3 种情况:
                    // (1)i == j,即 只有一个字符时,一定是回文串
                    // (2)i + 1 == j,即 有2个字符时,也是回文串
                    // (3)i + 1 < j,即 至少有3个字符时,则需要看 s[i+1, j-1] 是不是 回文串
                    if (i == j || i + 1 == j) { // (1)、(2)
                        dp[i][j] = true;
                    } else { // (3)多于2个字符
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                } else { // 当前字符 不相等,则 s[i, j] 一定不是 回文串
                    dp[i][j] = false;
                }
                if (dp[i][j]) res++;
            }
        }
        // 5、打印dp
        for (int i = 0; i < n; i++) {
            System.out.println(Arrays.toString(dp[i]));
        }
        return res;
    }
}

这里维护的是 dp 上三角

5. 最长回文子串

题目描述

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

提示:

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

dp 回文子串 & 回文子序列-leetcode-647. 回文子串 & leetcode-516. 最长回文子序列_第2张图片
和 647. 回文子串 基本类似,仍然是 回文子串(连续) 问题。

本题需要在 647. 回文子串 基础上 维护一个 maxLen、left、right(最长回文串的起始、终止位置),找到 最长的回文子串。

class Solution {
    public String longestPalindrome(String s) {
        int left = 0;
        int right = 0;
        int maxLen = 0;
        int n = s.length();
        // 1、dp[i][j] 表示 [i, j] 子串是否为 回文子串
        boolean[][] dp = new boolean[n][n];
        // 3、初始化(将 dp[i][j] 都置为 false 即可)

        // 4、遍历顺序(根据递推公式可知:从下向上,从左向右)
        for (int i = n - 1; i >= 0; i--) {
            for (int j = 0; j < n; j++) {
                // 2、递推公式
                if (s.charAt(i) == s.charAt(j)) {
                    if (j - i <= 1) { // 一个字符 或 2个字符且相等,都是 回文子串
                        dp[i][j] = true;
                    } else if (dp[i + 1][j - 1]) { // 当 len >=3 时,需要看 [i+1, j-1] 是否为 回文子串
                        dp[i][j] = true;
                    }
                }
                if (dp[i][j] && (j - i + 1) > maxLen) { // !!!差别
                    maxLen = j - i + 1;
                    left = i;
                    right = j;
                }
            }
        }
        System.out.println("left = " + left);
        System.out.println("right = " + right);
        return s.substring(left, right + 1); // !!!差别
    }
}

516. 最长回文子序列

题目描述

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

dp 回文子串 & 回文子序列-leetcode-647. 回文子串 & leetcode-516. 最长回文子序列_第3张图片
本题求的是 最长回文子序列,注意和 5. 最长回文子串 区别:

  • 子学历:可以不连续
  • 子串:必须连续

dp

dp 五步曲:

  1. dp[i][j] 表示 s[i, j] (闭区间) 中最长回文子序列(不一定连续)的长度
  2. 递推公式
    仍然分 2 大类情况,即 s[i]和s[j]相等、s[i]和s[j]不相等:
    • s[i]和s[j] 相等,只用看 s[i+1, j-1] 的最长回文子序列 + 2 即可,即 dp[i][j] = dp[i + 1][j - 1] + 2;
    • s[i]和s[j] 不相等,分 2 小种情况:
      • 1)删除 s[i],则看 s[i+1, j] 是否为 回文串,即 dp[i + 1][j];
      • 2)删除 s[j],则看 s[i, j-1] 是否为 回文串,即 dp[i][j - 1]
      • 二者,取 max
  3. 初始化
    dp[i][0]、dp[0][j] 均为 0;
    但是 dp[i][i] 在递推公式中 处理不到,所以 要初始化 dp[i][i] = 1
  4. 顺序
    由递推公式可知,当前状态依赖 下方、左边,所以 从下到上、从左到右
  5. 打印dp
class Solution {
    public int longestPalindromeSubseq(String s) {
        int n = s.length();
        int[][] dp = new int[n][n]; // dp[i][j] 表示 s[i, j] (闭区间) 中最长回文子序列(不一定连续)的长度
        // 3、初始化(dp[i][0]、dp[0][j] 均为 0;但是 dp[i][i] 在递推公式中 处理不到,所以 要初始化 dp[i][i] = 1)
        for (int i = 0; i < n; i++) dp[i][i] = 1;
        // 4、顺序(由递推公式可知,当前状态依赖 下方、左边,所以 从下到上、从左到右)
        for (int i = n - 1; i >= 0; i--) { // 从下到上
            for (int j = i + 1; j < n; j++) { // 从左到右(这里 j 必须从 i+1 开始,而不是从 i 开始)
                // 2、递推公式
                if (s.charAt(i) != s.charAt(j)) {
                    // 当前字符 不相等,分 2 中情况:
                    // (1)删除 s[i],则看 s[i+1, j] 是否为 回文串
                    // (2)删除 s[j],则看 s[i, j-1] 是否为 回文串
                    // 二者,取 max
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
                } else { // 当前字符 相等
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                }
            }
        }
        // 5、打印dp
        // for (int i = 0; i < n; i++) {
        //     System.out.println(Arrays.toString(dp[i]));
        // }
        return dp[0][n - 1];
    }
}

你可能感兴趣的:(leetcode,leetcode,算法,动态规划)