算法训练营Day48

#Java #动态规划

Feeling and experiences:

回文子串:力扣题目链接

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

输入:s = "abc"
输出:3
解释:三个回文子串: "a", "b", "c"

动态规划在 回文子串上的运用

之前做回文子串的问题,一般会用到 双指针 和 回溯

class Solution {
    public int countSubstrings(String s) {
        int n = s.length();  // 字符串的长度
        int ans = 0;  // 记录回文子串的个数
        for (int i = 0; i < 2 * n - 1; ++i) {
            int l = i / 2, r = i / 2 + i % 2;  // 根据奇偶性确定回文中心的左右边界
            while (l >= 0 && r < n && s.charAt(l) == s.charAt(r)) {
                --l;  // 向左扩展
                ++r;  // 向右扩展
                ++ans;  // 找到一个新的回文子串
            }
        }
        return ans;  // 返回最终的回文子串个数
    }
}

双指针的 思路很简单:

就是遍历每个可能的中心点,然后我们从 中心点 用两个指针开始遍历元素,判断是否为回文

至于 中心点 为什么是 2*n - 1 个:

来自力扣评论中的一条解释):

为什么是2n-1个中心点?

如果回文串是奇数,我们把回文串中心的那个字符叫做中心点,如果回文串是偶数我们就把中间的那两个字符叫做中心点

对于一个长度为n的字符串,我们可以用它的任意一个字符当做中心点,所以中心点的个数是n。我们还可以用它任意挨着的两个字符当做中心点,所以中心点是n-1,总的中心点就是2*n-1

动态规划的做法:

class Solution {
    public int countSubstrings(String s) {
        int len = s.length();
        boolean[][] dp = new boolean[len][len];  // dp[i][j] 表示从索引 i 到 j 的子串是否为回文
        int result = 0;  // 记录回文子串的个数
        for (int i = len - 1; i >= 0; i--) {
            for (int j = i; j < len; j++) {
                if (s.charAt(i) == s.charAt(j)) {
                    if (j - i <= 1) {
                        result++;  // 单个字符或相邻字符相同,是回文子串
                        dp[i][j] = true;
                    } else if (dp[i + 1][j - 1]) { 
                        result++;  // 利用动态规划,如果内部子串是回文,则整个子串也是回文
                        dp[i][j] = true;
                    }
                }
            }
        }
        return result;
    }
}
  • 使用动态规划数组 dp,其中 dp[i][j] 表示从索引 ij 的子串是否为回文。

  • 从字符串的最后一个字符开始,逆序遍历到第一个字符。这样可以确保在计算 dp[i][j] 时,dp[i+1][j-1] 已经被计算过,以便利用动态规划的结果。

  • 在每个位置 (i, j),检查字符 s.charAt(i)s.charAt(j) 是否相同,如果相同,判断子串是否为回文:

    • 如果 j - i 小于等于 1,说明子串长度为 1 或 2,一定是回文,此时增加回文子串的计数。
    • 如果 j - i 大于 1,利用动态规划的结果,如果内部子串是回文,则整个子串也是回文,同样增加回文子串的计数。

最长回文子序列:力扣题目链接

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

示例 1:

输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。

该题和 子串问题差不多

class Solution {
    public int longestPalindromeSubseq(String s) {
        // 本题是子序列问题,和子串问题类似
        // 创建dp数组,定义:区间[i,j] 左闭右闭 最大的回文子序列为dp[i][j]

        int[][] dp = new int[s.length()][s.length()];
        // 初始化为 java 默认为0

        // 循环,遍历
        for (int i = s.length() - 1; i >= 0; i--) {
            for (int j = i; j < s.length(); j++) {
                if (s.charAt(i) == s.charAt(j)) {
                    if (j - i <= 1) {
                        dp[i][j] = j - i + 1;
                    } else {
                        dp[i][j] = dp[i + 1][j - 1] + 2;
                    }
                } else {
                    dp[i][j] = Math.max(dp[i + 1][j], Math.max(dp[i][j - 1], dp[i][j]));
                }
            }
        }
        return dp[0][s.length() - 1];
    }
}
  • 从字符串的末尾开始循环,外层循环变量为 i,内层循环变量为 j。这样可以确保在计算 dp[i][j] 时,dp[i+1][j-1] 已经被计算过,以便利用动态规划的结果。

  • 对于每一对索引 (i, j),如果 s[i]s[j] 相等,那么:

    • 如果 j - i 小于等于 1,说明子串长度为 1 或 2,直接赋值 dp[i][j] = j - i + 1
    • 否则,利用动态规划的结果,dp[i][j] = dp[i + 1][j - 1] + 2
  • 如果 s[i]s[j] 不相等,那么 dp[i][j] 取左边、上边和左上角三个位置的最大值,即 dp[i][j] = Math.max(dp[i + 1][j], Math.max(dp[i][j - 1], dp[i][j]))

风不定,人初静,明日落红应满径。

Fighting!

你可能感兴趣的:(算法)