第九章_动态规划_总结篇 | 647. 回文子串、516.最长回文子序列、5.最长回文子串

LeetCode 647. 回文子串

        给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

视频讲解https://www.bilibili.com/video/BV17G4y1y7z9/?spm_id_from=333.788&vd_source=f98f2942b3c4cafea8907a325fc56a48文章讲解https://programmercarl.com/0647.%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.html

⭐如果s[i + 1, j - 1]是回文子串 且 s[i] == s[j] 👉s[i, j]是回文子串

  • 思路:
    • 思路一,动态规划:
      • dp数组含义:bool类型 dp[i][j] 表示 子串[i, j]是否回文
      • 递推公式:只用维护 i <= j 的 dp[i][j],考虑以下情况
        • 情况一:i == j,例如"a",true
        • 情况二:i + 1 == j,例如"aa",true
        • 情况三:i + 1 < j,看dp[i + 1][j - 1](此时dp[i + 1][j - 1]才有意义)
      • 初始化:全false
      • 遍历顺序:从下到上/从左到右
    • 思路二,双指针:
      • 遍历s,取中心点用于向两边对称位置扩展
        • 以 i 为中心(奇数)
        • 以 i 和 i + 1 为中心(偶数)
      • 从中心向两边比较,统计回文子串的数目
  • 代码:
// 思路一,动态规划:
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;
    }
};

// 简洁版:
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] && (j - i <= 1 || dp[i + 1][j - 1])) {
                    result++;
                    dp[i][j] = true;
                }
            }
        }
        return result;
    }
};
// 时间复杂度:O(n^2)
// 空间复杂度:O(n^2)
// 思路二,双指针:
class Solution {
public:
    int countSubstrings(string s) {
        int result = 0;
        for (int i = 0; i < s.size(); i++) {
            result += extend(s, i, i, s.size()); // 以i为中心
            result += extend(s, i, i + 1, s.size()); // 以i和i+1为中心
        }
        return result;
    }
    int extend(const string& s, int i, int j, int n) {
        int res = 0;
        while (i >= 0 && j < n && s[i] == s[j]) {
            i--;
            j++;
            res++;
        }
        return res;
    }
};
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)

LeetCode 516.最长回文子序列

        给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。

视频讲解https://www.bilibili.com/video/BV1d8411K7W6/?spm_id_from=333.788&vd_source=f98f2942b3c4cafea8907a325fc56a48文章讲解https://programmercarl.com/0516.%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.html

  • 思路:
    • dp数组含义:dp[i][j] 表示 子串[i, j]的最长回文子序列的长度
    • 递推公式:
      • s[i] 与 s[j] 相同👉dp[i][j] = dp[i + 1][j - 1] + 2;
      • s[i] 与 s[j] 不同👉dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);

    • 初始化dp[i][i] = 1;其余全0
    • 遍历顺序:从下到上,从左到右
    • 最终结果:dp[0][s.size() - 1]; 
  • 代码:
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];
    }
};

⭐拓展:LeetCode 5.最长回文LeetCode 5.最长回文子串LeetCode 5.最长回文

  • dp数组含义:dp[i][j] 表示 子串[i, j]的最长回文子串的长度
  • 递推公式:
    • s[i] 与 s[j] 相同 且 [i + 1, j - 1]是回文子串👉dp[i][j] = dp[i + 1][j - 1] + 2;
      • dp[i + 1][j - 1] == j - (i + 1)
      • 记录最长回文子串[start, end]
        • 每遇到一个回文子串[i, j]比较长度:j + 1 - i vs end + 1 - start
        • 若 >,则更新start、end
    • else👉dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
  • 初始化dp[i][i] = 1;其余全0
  • 遍历顺序:从下到上,从左到右
  • 最终结果:dp[0][s.size() - 1]👉字符串s的最长回文子串的长度
    • 题目要求返回最长回文子串
2023/3/17 二刷 5.最长回文子串:
        把dp数组设成bool型,只需用 pos 和 len 记录当前最长回文子串的 起始位置 和 长度,不必维护每个区间的最长回文子串长度!
class Solution {
public:
    string longestPalindrome(string s) {
        // 记录最长回文子串
        int pos = 0; // 起始位置
        int len = 0; // 长度
        // dp[i][j]:子串[i, j]是否回文
        vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
        // 从下到上,从左到右
        for (int i = s.size() - 1; i >= 0; --i) {
            for (int j = i; j < s.size(); ++j) {
                if (s[i] == s[j] && (j <= i + 1 || dp[i + 1][j - 1])) {
                    dp[i][j] = true;
                    // 更新最长回文子串
                    if (j - i + 1 > len) {
                        pos = i;
                        len = j - i + 1;
                    }
                }
            }
        }
        return s.substr(pos, len);
    }
};

动规五部曲:

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

动态规划基础: 

理论基础、509.斐波那契数、70.爬楼梯、746.使用最小花费爬楼梯:Day 38

62.不同路径Ⅰ、63.不同路径Ⅱ:Day 39

343.整数拆分、96.不同的二叉搜索树:Day 41

背包问题系列

01背包理论基础二维、一维、416:Day 42

1049、494、474:Day 43

完全背包理论基础、518、377:Day 44

70(进阶)、322、279:Day 45

139、多重背包、背包问题总结:Day 50

打家劫舍系列:Day 51

打家劫舍Ⅰ:线

打家劫舍Ⅱ:环

打家劫舍Ⅲ:树形dp

股票系列:Day 52Day 53Day 55

子序列系列:

300、674、718:Day 56

1143、1035、53:Day 57

392、115:Day 58

583、72:Day 59

647、516:Day 60

最长回文子串是指在一个字符串中最长回文子序列回文是指正着读和倒着读都一样的字符串。动态规划是解决最长回文子串问题的一种常用方法。动态规划的思想是将问题分解成子问题,通过求解子问题的最优解来得到原问题的最优解。在最长回文子串问题中,我们可以使用一个二维数组dp[i][j]来表示从i到j的子串是否为回文子串。如果dp[i][j]为true,则表示从i到j的子串回文子串,否则不是。我们可以通过以下步骤来求解最长回文子串: 1. 初始化dp数组,将所有dp[i][i]都设置为true,表示单个字符是回文子串。 2. 遍历字符串s,从长度为2的子串开始,依次判断每个子串是否为回文子串。如果是,则将dp[i][j]设置为true。 3. 在遍历的过程中,记录最长回文子串的长度和起始位置。 4. 最后,通过起始位置和长度来截取最长回文子串。 下面是一个示例代码,可以帮助你更好地理解动态规划求解最长回文子串的过程: class Solution { public: string longestPalindrome(string s) { int len=s.size(); if(len<2) return s; bool dp[len][len];//布尔型,dp[i][j]表示从i到j是否构成回文 int max_count=1;//最大字串的长度 int start=0;//最长字串的起始位置 for(int j=0;j<len;j++) { for(int i=0;i<j;i++) { if(s[i]!=s[j]) dp[i][j]=false; else if((j-i)<3)//(j-1)-(i+1)+1<2表示dp[i][j]的最大字串长度为1 dp[i][j]=true; else { dp[i][j]=dp[i+1][j-1]; } if((j-i+1)>max_count&&dp[i][j]) { max_count=j-i+1; start=i; } } } return s.substr(start,max_count);//截取字符串 } };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值