代码随想录算法训练营第46天 | LeetCode647.回文子串、 LeetCode516.最长回文子序列

目录

LeetCode647.回文子串

1. 动态规划

2.双指针法

LeetCode516.最长回文子序列


LeetCode647.回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

思路:在回溯系列也做过求给定字符串的所有回文子串,那里求的是所有的划分结果,这里统计的是回文子串的数目,但是因为回溯本质上是暴力求解,是在没有其他办法的情况下使用的一种方法,复杂度很大,呈指数级别,所以这里不采用回溯。

这里采用动态规划和双指针两种方法讲解。

1. 动态规划

首先我们定义dp[i][j]表示[i,j]区间内是否为回文子串。初始所有赋值为false。

当s[i]=s[j]时,如果是i==j,或者i+1==j,那么统计数目直接加1,并且置dp[i][j]=true;如果是j-i>1,那么当dp[i+1][j-1]为true,也就是说[i+1,j-1]区间范围内是回文子串时,这时候统计数目加1,同时dp[i][j]=true;

当s[i]!=s[j]时,可以知到dp[i][j]肯定为false了,因为初始化的时候已经设置了,所以这里可以不用进行操作。

从上面可以看到,dp[i][j]与dp[i+1][j-1]有关,而dp[i+1][j-1]处于dp[i][j]左下角,所以整体遍历顺序一定是从下到上的,所以i的顺序是从下到上的,j的话从左到右即可。

    int countSubstrings(string s) {
        //dp[i][j]表示以i下标开始,j下标结束的是否为子字符串
        vector> dp(s.size(), vector(s.size(), false));
        int count = 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){//这里相当于是将只有两个元素相等的情况以及一个元素的情况记录了下来
                        count ++;
                        dp[i][j] = true;
                    }else if(dp[i + 1][j - 1]){//这里是j-i的个数大于了2,所以只要dp[i+1][j-1],即[i+1,j-1]范围的元素构成回文,那么这里的区间[i,j]也能构成回文
                        count ++;
                        dp[i][j] = true;
                    }
                }
            }
        }
        return count;
    }

时间复杂度:O(n^2)

空间复杂度:O(n^2)

2.双指针法

这里还可以使用双指针法,核心思想就是从一个元素或者两个元素出发,不断向外扩展,最后统计所有的回文子串,返回最终结果即可。

    int getSubstring(string& s, int i, int j){
        int result = 0;//记录[i,j]区间内的回文子串数量
        while(i >= 0 && j < s.size() && s[i] == s[j]){
            result ++;
            i --;
            j ++;
        }
        return result;
    }
    int countSubstrings(string s) {
        int count = 0;//记录最终结果
        for(int i = 0; i < s.size(); i ++){
            count += getSubstring(s, i, i);//以[i,i这个区间一个元素为中心,尝试向两边扩展长度
            count += getSubstring(s, i, i + 1);//以[i,i+1]这个区间两个元素为中心,尝试向两边扩展长度
        }
        return count;
    }

时间复杂度:O(n^2)

空间复杂度:O(1)

LeetCode516.最长回文子序列

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

思路: 这里统计的是最长回文序列,是不连续的,上面的回文子串数目求的就是连续的。

这里dp[i][j]表示[i,j]区间内的最长回文序列的长度。

当s[i]=s[j]时,那么这个时候就等于dp[i+1][j-1]+2,表示最新的最长回文序列长度;

当s[i]!=s[j]时,可以知道s[i]、s[j]同时加入不会使得最长回文子序列长度增长,所以这里需要尝试将s[i]元素放入[i+1,j-1]区间内,于是有dp[i][j-1];将s[j]元素放入[i+1,j-1]区间内,于是有dp[i+1][j],按照从下到上,从左到右的顺序,这两个之前都已经计算过了,所以这里dp[i][j]就保留这两者中最大值的状态,即二者相比最长的回文子序列长度。

    int longestPalindromeSubseq(string s) {
        //dp[i][j]表示在[i,j]区间范围内的最长回文子序列的长度
        vector> dp(s.size(), vector(s.size(), 0));
        for(int i = 0; i < s.size(); i ++) dp[i][i] = 1;//这里初始化当区间元素个数为1时的最长回文子序列的长度为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][j - 1], dp[i + 1][j]);//当元素不相等时,尝试将s[i]元素添加进[i+1,j-1]区间内取最长回文序列长度,即dp[i][j-1],以及将s[j]元素加入[i+1,j-1]区间内取最长回文序列长度,即dp[i+1][j],二者取最大
                }
            }
        }
        return dp[0][s.size() - 1];
    }

时间复杂度:O(n^2)

空间复杂度:O(n^2)

感谢你的阅读,希望我的文章能够给你帮助,如果有帮助,麻烦点赞加收藏,或者点点关注,非常感谢。

如果有什么问题欢迎评论区讨论!

 

你可能感兴趣的:(算法,数据结构,动态规划)