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

647. 回文子串

文档讲解 : 代码随想录 - 647. 回文子串
状态:再次回顾。

动态规划五部曲:

  1. 确定dp数组(dp table)以及下标的含义
    dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果dp[i][j]true,否则为false

  2. 确定递推公式

    • s[i]s[j]不相等,dp[i][j]一定是false
    • s[i]s[j]相等时,有如下三种情况:
      • 情况一:下标ij相同,同一个字符例如a,当然是回文子串
      • 情况二:下标ij差为1,例如aa,也是回文子串
      • 情况三:下标ij差大于1的时候,例如cabac,此时s[i]s[j]已经相同了,我们看ij区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是i+1j-1区间,这个区间是不是回文就看dp[i + 1][j - 1]是否为true
    if (s[i] == s[j]) {
        if (j - i <= 1) { // 情况一 和 情况二
            result++;
            dp[i][j] = true;
        } else if (dp[i + 1][j - 1]) { // 情况三
            result++;
            dp[i][j] = true;
        }
    }
    
  3. dp数组初始化
    dp[i][j]初始化为false

  4. 确定遍历顺序
    从递推公式中可以看出,情况三是根据dp[i + 1][j - 1]是否为true,在对dp[i][j]进行赋值true的。dp[i + 1][j - 1]dp[i][j]左下角,如图:
    代码随想录算法训练营第五十七天|647. 回文子串、516.最长回文子序列、动态规划总结篇_第1张图片
    所以一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]都是经过计算的。

  5. 举例推导dp数组:
    举例,输入:"aaa"dp[i][j]状态如下:
    代码随想录算法训练营第五十七天|647. 回文子串、516.最长回文子序列、动态规划总结篇_第2张图片

本体代码:

class Solution {
public:
    int countSubstrings(string s) {
        vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
        int result = 0;
        for (int i = s.size() - 1; i >= 0; i--) {  // 注意遍历顺序
            for (int j = i; j < s.size(); j++) {
                if (s[i] == s[j]) {
                    if (j - i <= 1) { // 情况一 和 情况二
                        result++;
                        dp[i][j] = true;
                    } else if (dp[i + 1][j - 1]) { // 情况三
                        result++;
                        dp[i][j] = true;
                    }
                }
            }
        }
        return result;
    }
};
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2)

516.最长回文子序列

文档讲解 : 代码随想录 - 516.最长回文子序列
状态:再次回顾。

回文子串是要连续的,回文子序列可不是连续的!

动态规划五部曲:

  1. 确定dp数组(dp table)以及下标的含义
    dp[i][j]:字符串s[i, j]范围内最长的回文子序列的长度为dp[i][j]

  2. 确定递推公式
    代码随想录算法训练营第五十七天|647. 回文子串、516.最长回文子序列、动态规划总结篇_第3张图片
    代码随想录算法训练营第五十七天|647. 回文子串、516.最长回文子序列、动态规划总结篇_第4张图片

    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]);
    }
    
  3. dp数组初始化
    从递推公式:dp[i][j] = dp[i + 1][j - 1] + 2; 可以看出 递推公式是计算不到ij相同时候的情况。
    所以需要手动初始化一下,当ij相同,那么dp[i][j]一定是等于1的,即:一个字符的回文子序列长度就是1。其他情况dp[i][j]初始为0,这样递推公式:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);dp[i][j]才不会被初始值覆盖。

    vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
    for (int i = 0; i < s.size(); i++) dp[i][i] = 1;
    
  4. 确定遍历顺序
    从递归公式中,可以看出,dp[i][j]依赖于dp[i + 1][j - 1]dp[i + 1][j]dp[i][j - 1],如图:
    代码随想录算法训练营第五十七天|647. 回文子串、516.最长回文子序列、动态规划总结篇_第5张图片

所以一定要从下到上,从左到右遍历,这样保证下一行的数据都是经过计算的。

  1. 举例推导dp数组:
    输入s:"cbbd" 为例,dp数组状态如图:
    代码随想录算法训练营第五十七天|647. 回文子串、516.最长回文子序列、动态规划总结篇_第6张图片

本题代码:

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
        for (int i = 0; i < s.size(); i++) dp[i][i] = 1;
        for (int i = s.size() - 1; i >= 0; i--) {
            for (int j = i + 1; j < s.size(); 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[0][s.size() - 1];
    }
};
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2)

动态规划总结篇

文档讲解 : 代码随想录 - 动态规划总结篇
状态:再次回顾。

动规五部曲分别为:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

动态规划基础

第三十八天、第三十九天、第四十一天

背包问题系列

代码随想录算法训练营第五十七天|647. 回文子串、516.最长回文子序列、动态规划总结篇_第7张图片

打家劫舍系列

股票系列

代码随想录算法训练营第五十七天|647. 回文子串、516.最长回文子序列、动态规划总结篇_第8张图片

子序列系列

代码随想录算法训练营第五十七天|647. 回文子串、516.最长回文子序列、动态规划总结篇_第9张图片

总结

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