动态规划 —— 回文串问题

找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程程(ಥ_ಥ)-优快云博客

所属专栏:动态规划

目录

647.回文子串

5.最长回文子串

1745.分割回文串IV

132.分割回文串II

516.最长回文子序列

1312.让字符串成为回文串的最少插入次数


647.回文子串

题目:516.最长回文子序列

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

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

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

示例 1:

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

示例 2:

输入:s = "aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"

提示:

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

思路:

代码实现:

class Solution {
    public int countSubstrings(String s) {
        // 创建dp表、初始化、填表、返回值
        int n = s.length();
        boolean[][] dp = new boolean[n][n];
        char[] ss = s.toCharArray();

        // 初始化通过for循环来完成

        int ret = 0;
        for (int i = n-1; i >= 0; i--) { // 从下往上
            for (int j = n-1; j >= i; j--) { // 从右往左
                if (ss[i] == ss[j]) {
                    if (i == j || i+1 == j) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] =  dp[i+1][j-1];
                    }
                } // 默认为false,因此不用去赋值了
                ret += dp[i][j] ? 1 : 0;
            }
        }

        return ret;
    }
}

5.最长回文子串

题目:

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

示例 1:

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

示例 2:

输入:s = "cbbd"
输出:"bb"

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成

思路:

本题与上一题唯一的不同,就是需要在所有的回文子串中找出最长的回文子串。处理方法,既可以是在填完dp表之后,去遍历求出最长的回文子串,也可以在填dp表时,就去统计最长回文子串的信息。

代码实现:

class Solution {
    public String longestPalindrome(String s) {
        int n = s.length();
        boolean[][] dp = new boolean[n][n];
        char[] ss = s.toCharArray();

        // 初始化的工作通过 for循环 完成
        int[] ret = new int[2]; // 记录最长回文子串的起始位置和结束位置
        for (int i = n-1; i >= 0; i--) {
            for (int j = n-1; j >= i; j--) {
                if (ss[i] == ss[j]) {
                    if (i == j || i+1 == j) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i+1][j-1];
                    }
                }
                // 更新最长回文子串的信息
                if (dp[i][j] && j - i > ret[1] - ret[0]) {
                    ret[0] = i;
                    ret[1] = j;
                }
            }
        }

        return s.substring(ret[0], ret[1]+1); // 注意substring方法是左闭右开的
    }
}

1745.分割回文串IV

题目:

给你一个字符串 s ,如果可以将它分割成三个 非空 回文子字符串,那么返回 true ,否则返回 false 。

当一个字符串正着读和反着读是一模一样的,就称其为 回文字符串 。

示例 1:

输入:s = "abcbdd"
输出:true
解释:"abcbdd" = "a" + "bcb" + "dd",三个子字符串都是回文的。

示例 2:

输入:s = "bcbddxy"
输出:false
解释:s 没办法被分割成 3 个回文子字符串。

提示:

  • 3 <= s.length <= 2000
  • s​​​​​​ 只包含小写英文字母。

思路:题目是让我们判断原字符串分割为三部分子字符串之后,判断这些子字符串是否是回文串。怎么分割成三部分呢?我们可以通过枚举的方式,枚举出分割点的位置,从而将字符串分为了[0, i-1]、[i, j]、[j+1, n-1]这三部分,接下来就是判断这三部分是否为回文子串。而通过dp表可以快速查阅子串是否为回文串。因此本题最终的处理思路是,先将dp表构建出来,然后去枚举分割点,判断分割后的字符串是否为回文串即可。

代码实现:

class Solution {
    public boolean checkPartitioning(String s) {
        // 1、构建完整的dp表信息
        int n = s.length();
        char[] ss = s.toCharArray();
        boolean[][] dp = new boolean[n][n];

        for (int i = n-1; i >= 0; i--) {
            for (int j = n-1; j >= i; j--) {
                if (ss[i] == ss[j]) {
                    if (i == j || i+1 == j) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i+1][j-1];
                    }
                }
            }
        }

        // 2、枚举所有的分割点,判断是否为回文子串
        // 枚举时,得确保 [0, i-1]、[i, j]、[j+1, n-1]区间都得有值
        // 从而得出边界值(i-1 >= 0,因此 i>=1, j>=i, j+1 <= n-1,因此j<=n-2)
        for (int i = 1; i < n-1; i++) {
            for (int j = i; j < n-1; j++) {
                // 判断三个子串是否都回文
                if (dp[0][i-1] && dp[i][j] && dp[j+1][n-1]) {
                    return true;
                }
            }
        }

        return false;
    }
}

132.分割回文串II

题目:

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文串。

返回符合要求的 最少分割次数 。

示例 1:

输入:s = "aab"
输出:1
解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。

示例 2:

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

示例 3:

输入:s = "ab"
输出:1

提示:

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

思路:注意:dp表只能存储子串是否回文的信息,而不能进行其他操作。因此还得根据前面写动态规划的步骤来解决本题。

代码实现:

class Solution {
    public int minCut(String s) {
        // 创建dp表、初始化、填表、返回值
        int n = s.length();
        int[] dp = new int[n];
        // 存储子串是否回文信息
        boolean[][] bool = palindromeSubstring(n, s);

        // // 初始化dp数组
        // Arrays.fill(dp, Integer.MAX_VALUE);

        // for (int i = 0; i < n; i++) {
        //     // 判断[0,i]是否为回文串
        //     if (!bool[0][i]) {
        //         for (int j = 1; j <= i; j++) {
        //             // 判断[j,i]是否为回文串
        //             if (bool[j][i]) {
        //                 dp[i] = Math.min(dp[i], dp[j-1]+1);
        //             }
        //         }
        //     } else {
        //         dp[i] = 0;
        //     }
        // }


        for (int i = 0; i < n; i++) {
            // 判断[0,i]是否为回文串
            if (!bool[0][i]) {
                int min = Integer.MAX_VALUE;
                for (int j = 1; j <= i; j++) {
                    // 判断[j,i]是否为回文串
                    if (bool[j][i]) {
                        min = Math.min(min, dp[j-1]+1);
                    }
                }
                dp[i] = min;
            }
            // 默认为0,因此无需更新
        }

        return dp[n-1];
    }


    private boolean[][] palindromeSubstring(int n, String s) {
        // 创建dp表、初始化、填表、返回值
        boolean[][] bool = new boolean[n][n];

        for (int i = n-1; i >= 0; i--) {
            for (int j = n-1; j >= i; j--) {
                if (s.charAt(i) == s.charAt(j)) {
                    if (i == j || i+1 == j) {
                        bool[i][j] = true;
                    } else {
                        bool[i][j] = bool[i+1][j-1];
                    }
                }
            }
        }

        return bool;
    }
}

516.最长回文子序列

题目:

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

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

示例 1:

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

示例 2:

输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。

提示:

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

思路:

代码实现:

class Solution {
    public int longestPalindromeSubseq(String s) {
        // 创建dp表、初始化、填表、返回值
        int n = s.length();
        char[] ss = s.toCharArray();
        int[][] dp = new int[n][n];

        // 无需初始化

        for (int i = n-1; i >= 0; i--) {
            for (int j = i; j < n; j++) {
                if (ss[i] == ss[j]) {
                    if (i == j) {
                        dp[i][j] = 1;
                    } else if (i+1 == j) {
                        dp[i][j] = 2;
                    } else {
                        dp[i][j] = dp[i+1][j-1] + 2;
                    }
                } else {
                    dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
                }
            }
        }

        // 返回的是 [0,n-1] 区间内,最长子序列的长度
        return dp[0][n-1];
    }
}

1312.让字符串成为回文串的最少插入次数

题目:

给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。

请你返回让 s 成为回文串的 最少操作次数 。

「回文串」是正读和反读都相同的字符串。

示例 1:

输入:s = "zzazz"
输出:0
解释:字符串 "zzazz" 已经是回文串了,所以不需要做任何插入操作。

示例 2:

输入:s = "mbadm"
输出:2
解释:字符串可变为 "mbdadbm" 或者 "mdbabdm" 。

示例 3:

输入:s = "leetcode"
输出:5
解释:插入 5 个字符后字符串变为 "leetcodocteel" 。

提示:

  • 1 <= s.length <= 500
  • s 中所有字符都是小写字母。

思路: 

代码实现: 

class Solution {
    public int minInsertions(String s) {
        // 创建dp表、初始化、填表、返回值
        int n = s.length();
        char[] ss = s.toCharArray();
        int[][] dp = new int[n][n];

        // 无需初始化

        // 由下到上、由左到右
        for (int i = n-1; i >= 0; i--) {
            for (int j = i; j < n; j++) {
                if (ss[i] == ss[j]) {
                    if (i == j) {
                        dp[i][j] = 0;
                    } else if (i+1 == j) {
                        dp[i][j] = 0;
                    } else {
                        dp[i][j] = dp[i+1][j-1];
                    }
                } else {
                    dp[i][j] = Math.min(dp[i+1][j], dp[i][j-1]) + 1;
                }
            }
        }

        // 求整个字符串的变成回文串的最少操作次数
        return dp[0][n-1];
    }
}

好啦!本期 动态规划 —— 回文串问题 的刷题之旅 就到此结束啦!我们下一期再一起学习吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我要学编程(ಥ_ಥ)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值