代码随想录算法训练营第五十七天|647. 回文子串、516.最长回文子序列

LeetCode 647. 回文子串

链接:647. 回文子串

思路:

这题目既可以用双指针做也可以用动态规划做,由于双指针不是本篇主要内容,所以主要关注动态规划做法。首先定义下标,dp[i][j]表示字符串s中下标为i到j的子字符串是否为回文,所以dp实际上是一个二维的布尔数组。然后我们首先把dp[0][0]初始化为true,因为dp[0][0]代表的就是字符串的第一个字符,单个字符一定是一个回文,这么做是为了给后序的递推操作做准备。同时,因为我们要计算回文子串的个数,声明变量ans并初始化为1,这时我们已经有了dp[0][0]这一个回文。

接下来确定递推公式。比较字符s[i]和s[j]是否一致,有两种不同的情况:

  1. s[i] != s[j]。这就表明子字符串的头尾不一致(根据定义,子字符串为下标[j, i]的字符串,所以s[j]代表子字符串的尾部,s[i]代表头部),这时候无论如何子字符串都不可能是回文,所以dp[i][j] = false。
  2. s[i] == s[j]。当子字符串头尾一致的时候,根据i和j的关系判断。如果i和j相差小于等于1,也就是子字符串长度为1或者2,这个时候子字符串一定为回文,dp[i][j] = true。如果i和j相差2个及以上,说明子字符串长度大于2,这个时候要判断去头去尾后的子字符串是否为回文,因为头和尾已经确定了一致,接下来只要保证中间也是回文就可以了。去头和尾后的子字符串下标分别为i-1和j+1,所以dp[i][j] = dp[i-1][j+1]。然后根据dp[i][j]的值累计回文数量。

递推公式就推导完了,还需要确定遍历顺序。根据递推公式,dp[i][j]依赖于dp[i-1][j+1]的结果,也就是说i依赖于它的前一位i-1,j依赖于它的后一位j+1,所以i是正序遍历,从1到s.size(),j是倒序遍历,从i到0。注意j不能是正序遍历,否则的话它就利用了还没计算好的j+1的值了。

代码:

class Solution {
public:
    int countSubstrings(string s) {
        // 定义下标:dp[i][j]表示字符串s中下标为j到i的子字符串是否为回文
        vector> dp(s.size(), vector(s.size()));
        dp[0][0] = 1;
        int ans = 1;
        for (int i = 1; i < s.size(); i++)
        {
            for (int j = i; j >= 0; j--)
            {
                if (s[i] != s[j])
                    dp[i][j] = false;
                else if (i == j || i - j == 1)
                {
                    dp[i][j] = true;
                    ans++;
                }
                else
                {
                    dp[i][j] = dp[i-1][j+1];
                    if (dp[i][j])
                        ans++;
                }
            }
        }
        return ans;
    }
};

LeetCode 516.最长回文子序列

链接:516.最长回文子序列

思路:

和上一题十分相似,但还是有略微的区别。先定义下标:dp[i][j]表示字符串s中下标为i到j的最长回文子字符串长度。注意这里dp代表的不是是否为回文,而是回文的长度了。根据定义初始化数组,因为单个的字符一定是回文,所以可以把dp数组的对角线上的值都初始化为1(对角线上i和j相等,子字符串长度为1)。然后是递推公式,同样分两种情况:

  1. s[i] == s[j]。这个时候还是看去头和尾之后的回文长度,因为子字符串可以是不连续的,不论去头尾后的子字符串长度如何,只要确定了它的回文长度,就可以知道加上头和尾之后的长度了,它们相差2(一头一尾两个字符),所以dp[i][j] = dp[i-1][j+1] + 2。
  2. s[i] != s[j]。这里就和之前做的最长相同子字符串有点类似了,我们有两种做法:1、取当字符i不取字符j,回文长度dp[i][j+1]。2、取字符j不取字符i,回文长度dp[i-1][j]。只需要在这两个里面找最大的那个就可以了,dp[i][j] = max(dp[i-1][j], dp[i][j+1])。

最后返回代表整个字符串长度的dp[s.size()-1][0]。

代码:

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        // 定义下标:dp[i][j]表示字符串s中下标为i到j的最长回文子字符串长度
        vector> dp(s.size(), vector(s.size()));
        for (int i = 0; i < s.size(); i++)  dp[i][i] = 1;
        for (int i = 1; i < s.size(); i++)
        {
            for (int j = i - 1; j >= 0; j--)
            {
                if (s[i] == s[j])
                    dp[i][j] = dp[i-1][j+1] + 2;
                else
                    dp[i][j] = max(dp[i-1][j], dp[i][j+1]);
            }
        }
        return dp[s.size()-1][0];
    }
};

你可能感兴趣的:(代码随想录算法训练营,算法,leetcode,动态规划)