代码随想录算法训练营第三十九天,647.回文子串,516.最长回文子序列,动态规划总结篇。

今日内容:647.回文子串,516.最长回文子序列,动态规划总结篇。
心得:长达11天的动态规划结束了,接下来是图论。下周开始背八股了。

647.回文子串

首先要对回文子串的特点进行分析,如果已知s[1]、s[2]、s[3]是回文子串,那么只需要判断s[0]和s[4]是否相等。
即判断一个子字符串(范围[i, j])是否回文,依赖于子字符串(范围[i + 1, j - 1])是否回文。

  1. 确定dp数组以及下标的含义:dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的⼦串是否是回⽂⼦串,如果是dp[i][j]为true,否则为false。
  2. 确定递推公式:如果s[i] != s[j],则dp[i][j] = false;如果s[i] = s[j],有三种情况:(1)i = j,代表同一个字符,例如a,一定是回文子串;(2)j - i = 1,例如aa,也是回文子串;(3)j - i > 1,例如abcba,只需要判断dp[i + 1][j - 1],即bcb是不是回文子串,如果是就返回true。
if(s[i] != s[j]) dp[i][j] = false;
if(s[i] == s[j]){
    if(j - i <= 1) {
        result++;
        dp[i][j] = true;
    }
    else{
        if(dp[i + 1][j - 1] == true){
            result++;
            dp[i][j] = true;
        }
        else dp[i][j] = false;
    }
}
  1. dp数组如何初始化:根据递推公式可知dp[i][0]一定需要初始化,表示子字符串范围[i,0]是否是回文子串,很明显,当i = 0时,只有一个字符,一定是回文子串,其余都不是回文子串。则dp[0][0] = true,其余初始化为false。(但是因为j是从=i开始的,因此相当于dp[0][0] = true的初始化在循环中可以实现,所以直接全部设置为false就好了。)
  2. 确定遍历顺序:可以看到i是依赖i + 1,j依赖j - 1,那么遍历顺序是从下到上,从左到右的。
  3. 举例推导dp数组
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);

516.最长回文子序列

需要找出最长的回文子序列,并返回长度。回文子串是连续的,但是回文子序列不一定是连续的。

  1. 确定dp数组以及下标的含义:dp[i][j]:字符串s在[i, j]范围内最⻓的回⽂⼦序列的⻓度为dp[i][j]。
  2. 确定递推公式:如果s[i]与s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2,如果s[i]与s[j]不相同,说明s[i]和s[j]的同时加⼊并不能增加[i,j]区间回⽂⼦序列的⻓度,那么分别加⼊s[i]、s[j]看看,哪⼀个可以组成最⻓的回⽂⼦序列。dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
  3. dp数组如何初始化:递推公式是计算不到i和j相同时候的情况。所以手动初始化当i=j时,dp[i][j] = 1,其余初始化为0。
  4. 确定遍历顺序:根据递推公式同样是从下到上,从左向右的遍历顺序。
  5. 举例推导dp数组
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)。

动态规划总结篇

模版如下

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

对于每道题,在草稿上写出dp数组的含义和递推公式就成功了一大半。

最长回文子序列(Longest Palindromic Subsequence,LPS)问题是指在一个给定的字符串中找到一个最长回文子序列回文子序列是指一个序列本身不是回文串,但它是一个回文串的子序列。 在C++中,我们可以使用动态规划(Dynamic Programming,DP)的方法来解决这个问题。动态规划的主要思想是将一个大问题分解成小问题,然后从小问题出发,逐渐求得大问题的解。 以下是一个使用动态规划解决最长回文子序列问题的C++示例代码: ```cpp #include <iostream> #include <vector> #include <string> using namespace std; // 函数用于计算字符串str的最长回文子序列的长度 int longestPalindromeSubseq(string str) { int n = str.size(); // 创建一个二维数组dp,用于存储子问题的解,初始化所有值为0 vector<vector<int>> dp(n, vector<int>(n, 0)); // 单个字符的最长回文子序列长度为1,所以对角线上的元素设置为1 for (int i = 0; i < n; i++) { dp[i][i] = 1; } // 如果两个字符相同,那么它俩组成的子序列长度为2 for (int cl = 2; cl <= n; cl++) { for (int i = 0; i < n - cl + 1; i++) { int j = i + cl - 1; if (str[i] == str[j] && cl == 2) { dp[i][j] = 2; } else if (str[i] == str[j]) { dp[i][j] = dp[i + 1][j - 1] + 2; } else { dp[i][j] = max(dp[i][j - 1], dp[i + 1][j]); } } } // 返回整个字符串的最长回文子序列长度 return dp[0][n - 1]; } int main() { string str; cout << "请输入一个字符串:" << endl; cin >> str; cout << "最长回文子序列的长度为:" << longestPalindromeSubseq(str) << endl; return 0; } ``` 在这段代码中,`dp[i][j]`表示从字符串的第`i`个字符到第`j`个字符组成的子串的最长回文子序列的长度。通过初始化对角线以及递推式逐步填充这个二维数组,最终可以得到整个字符串的最长回文子序列长度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值