动态规划:回文串问题

目录

题目一:回文子串

题目二:最长回文串

题目三:回文串分割IV

题目四:回文串分割II


题目一:回文子串

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

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

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

示例 1:

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

示例 2:

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

这里需要注意的一点就是:子串必须是连续的,并且只要开始和结束位置不同,是可以出现相同的子串的

①状态表示

因为是需要枚举两次字符串才能得到所有的子串,所以我们可以创建一个二维的dp表,dp[i][j]

其中 i 表示行,j 表示列

i 小于等于 j

②状态转移方程

此时分为两大类,s[i]和s[j]是否相等:

如果两边的s[i]和s[j]都不同,那就返回false

如果相同,又细分为三种情况:重叠、相邻、不相邻

重叠和相邻直接返回true,如果不相邻,则需要继续看dp[i+1][j-1]的情况

③初始化

因为上述状态转移方程考虑到了,重叠和相邻这两种可能越界的情况,所以这里就不需要初始化了

④填表顺序

顺序是从下往上的,因为dp[i][j]用到了dp[i+1][j-1]的值

⑤返回值

所以返回值就是dp表中true的个数

代码如下:

class Solution 
{
public:
    int countSubstrings(string s) 
    {
        int n = s.size(), ret = 0;
        vector<vector<bool>> dp(n, vector<bool>(n));
        for(int i = n - 1; i >= 0; i--)
        {
            for(int j = i; j < n; j++)
            {
                // 不需要处理不同的情况,dp表默认就是false
                if(s[i] == s[j])
                { 
                    // 如果 i+1<j 说明 i 和 j 不重叠、不相邻,否则就是true
                    dp[i][j] = i + 1 < j ? dp[i+1][j-1] : true;
                    // 统计结果
                    if(dp[i][j] == true) ret++;
                }
            }
        }
        return ret;
    }
};

题目二:最长回文串

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

示例 1:

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

示例 2:

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

最长回文子串,与上一题的思想基本相同,同样是创建一个二维的dp表,用于表示 i 位置开始 j 位置结束的子串是否是回文子串

所以这道题只需要在每个位置判断是否是回文子串,如果是则比较一下是否是当前最长的回文子串,得到其实位置和最大长度,就可以使用substr得到该子串了

代码如下:

class Solution 
{
public:
    string longestPalindrome(string s) 
    {
        int n = s.size();
        int len = 1, begin = 0;
        vector<vector<bool>> dp(n, vector<bool>(n));
        for(int i = n - 1; i >= 0; i--)
        {
            for(int j = i; j < n; j++)
            {
                if(s[i] == s[j])
                    dp[i][j] = i + 1 < j ? dp[i+1][j-1] : true;
                if(dp[i][j] == true && j - i + 1 > len)
                {
                    begin = i;
                    len = j - i + 1;
                }
            }
        }
        return s.substr(begin, ret);
    }
};

题目三:回文串分割IV

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

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

示例 1:

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

示例 2:

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

本题依旧是需要创建dp表,表示每个位置是否是回文子串

与之前的做法不同的是,本题要求将字符串分割为三个回文串,想要成功分割三个回文串,只需要分为三部分,[0, i - 1],[i, j],[j + 1, n - 1],只要存在这三部分都是回文串,就能够满足题目要求

所以我们只需要枚举第二个子串的开头结尾,就能够遍历上述所有情况,因为是枚举的第二个子串,所以不能从0下标开始,需要从1开始,且最后一个位置不能是n-1,最后一个位置是n-2,因为至少需要给第三个子串留一个位置

代码如下:

class Solution 
{
public:
    bool checkPartitioning(string s) 
    {
        int n = s.size();
        vector<vector<bool>> dp(n, vector<bool>(n));
        // 所有子串是否是回文进行预处理
        for(int i = n - 1; i >= 0; i--)
            for(int j = i; j < n; j++)
                if(s[i] == s[j])
                    dp[i][j] = i + 1 < j ? dp[i+1][j-1] : true;
        // 枚举所有第二个字符串的起始位置及结束位置
        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;
    }
};

题目四:回文串分割II

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

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

示例 1:

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

示例 2:

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

示例 3:

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

①状态表示

②状态转移方程

dp[i]有两种情况:

0 ~ i 是回文串,dp[i] = 0
0 ~ i 不是回文串,在 0 < j ≤ i 的范围内,判断 j ~ i 是否是回文,如果是:回文串分割的次数就为dp[j - 1] + 1,所以 dp[i] = min(dp[j - 1] + 1, dp[i]);

③初始化

在计算 dp[i] = min(dp[j - 1] + 1, dp[i]) 时,为了不影响 min,所以将 dp 表内全部的值全部初始化为无穷大

④填表顺序

从左往右

⑤返回值

返回dp[n-1]

代码如下:

class Solution 
{
public:
    int minCut(string s) 
    {
        int n = s.size();
        // 先动归处理 i开头j结尾 的子串是否是回文
        vector<vector<bool>> prev(n, vector<bool>(n));
        for(int i = n - 1; i >= 0; i--)
            for(int j = i; j < n; j++)
                if(s[i] == s[j])
                    prev[i][j] = i + 1 < j ? prev[i+1][j-1] : true;
        // 再动归处理 0 ~ i 位置的的子串最少需要分隔的次数
        vector<int> dp(n, INT_MAX);
        for(int i = 0; i < n; i++)
        {
            if(prev[0][i]) dp[i] = 0;
            else
            {
                // 0~i 不是回文,就在 1 ~ i 的区间找是回文串的子串
                for(int j = 1; j <= i; j++)
                    if(prev[j][i])
                        dp[i] = min(dp[j-1] + 1, dp[i]);
            }
        }
        return dp[n-1];
    }
};

动态规划:回文串问题 到此结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值