当前位置:   article > 正文

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

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

Leetcode 647. 回文子串

题目链接:Leetcode 647. 回文子串
题目描述: 给你一个字符串 s ,请你统计并返回这个字符串中回文子串的数目。

  • 回文字符串是正着读和倒过来读一样的字符串。
  • 子字符串是字符串中的由连续字符组成的一个序列。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

思路: 本题可以利用动态规划来解决。本题的关键逻辑是:判断s[i]是否等于s[j]由于判断回文子串需要依次对比字符串的两侧字符,用一维数组不能很好的表示递推关系,因此需要利用二维数组。

  • 定义dp[i][j][i , j]闭区间内的字符串是否为回文子串,是为true;不是为false
  • 递推公式:如果s[i] != s[j],返回false;如果s[i] == s[j],并且当ij相差小于等于1的时候,一定是回文子串,返回true,否则需要让ij分别向内收缩一位(变成dp[i + 1][j - 1]),重复上述判断。
  • 初始化:对于任意[i , i]区间,都一定是回文子串,因此dp[i,i] = true,其余均为false
  • 遍历顺序:根据递推公式我们发现外层遍历i需要从后向前,内层遍历j需要从前向后。
  • 结果:dp[i][j] == true的个数

代码如下:(dp)

class Solution {
public:
    int countSubstrings(string s) {
        int n = s.size();
        vector<vector<bool>> dp(n + 5, vector<bool>(n + 5, false));
        int result = 0;
        for (int i = n - 1; i >= 0; i--)//注意遍历顺序
            for (int j = i; j < n; j++) {
                if (s[i] == s[j] && (j - i <= 1 || dp[i + 1][j - 1])) {
                    result++;
                    dp[i][j] = true;
                }
            }
        return result;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2)

既然断回文子串需要依次对比字符串的两侧字符,那我们也可以直接用双指针法来解决。为了使代码通俗易懂,我们可以将字符数为奇数的字符串和字符数为偶数的字符串分开计算。

代码如下:(双指针法)

class Solution {
public:
    int countSubstrings(string s) {
        int n = s.size();
        int result = 0;
        for(int i = 0; i < n; i ++ )
        {
            result += extend(s, i, i);//奇数个字符的字符串
            result += extend(s, i, i + 1); //偶数个字符的字符串
        }
        return result;
    }
    int extend(const string& s,int i,int j)//判断[i,j]范围内是否是回文子串
    {
        int res = 0;
        while(i >= 0 && j < s.size() && s[i] == s[j])//范围逐渐扩大
        {
            i -- ;
            j ++ ;
            res ++ ;
        }
        return res;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( 1 ) O(1) O(1)

Leetcode 516.最长回文子序列

题目链接:Leetcode 516.最长回文子序列
题目描述: 给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

思路: 本题需要求回文子序列,也就是字符之间可以不连续,这样双指针法就不太容易实现了。不过本题还是可以利用动态规划来解决。本题的关键逻辑和上题相同:判断s[i]是否等于s[j]

  • dp[i][j]:字符串s[i, j]范围内最长的回文子序列的长度
  • 如果s[i]s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2;如果s[i]s[j]不相同,那么分别加入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()]

代码如下:(dp)

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n = s.size();
        vector<vector<int>> dp(n + 5, vector<int>(n + 5, 0));
        for(int i = 0; i < n; i ++ ) dp[i][i] = 1;//初始化
        for(int i = n - 1; i >= 0; i -- )
            for(int j = i + 1; j < n; 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][n - 1];
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2)

总结: 尽管动态规划题目到这里已经结束,但是真正掌握dp还远不如此,与君共勉!

最后,如果文章有错误,请在评论区或私信指出,让我们共同进步!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/836479
推荐阅读
相关标签
  

闽ICP备14008679号