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

647.回文子串、516.最长回文子序列

647.回文子串

暴力动态规划

对于回文子串,回文表示,一个字符串是镜像的,也就是说这个字符串从前向后与从后向前对应的字符是相同的。
回文子串,表示的是镜像的连续子串。
对此,我们可以用动态规划的思路去解决这一道题目。
用dp数组记录回文子串的数量。dp[ii]表示截止到ii所包含的回文子串数量。其值等于截止到ii-1所包含的回文子串数量加上以ii结尾的回文子串数量。因此,我们可以写一个函数来判断一个字符串是不是回文的。之后只要从前向后遍历到ii判断这个字符串是否回文即可。

判断是否回文

我们要判断一个子串是否回文,我们只需一直对比首尾元素即可。如果存在一个元素不相同,就返回false,否则返回true。

bool huiwen(string &s,int start,int end)
{
    while(start<end)
    {
        if(s[start]!=s[end]) return false;
        start++;
        end--;
    }
    return true;
}

动态规划四部曲。
1.dp数组的含义。dp[ii]表示截止到ii所包含的回文子串数量。
2.dp数组的推导公式。dp[ii]等于截止到ii-1所包含的回文子串数量加上以ii结尾的回文子串数量。

            int temp=0;
            for(int jj =0;jj<=ii;jj++)
                if(huiwen(s,jj,ii))
                    temp++;
            dp[ii] = dp[ii-1]+temp;

3.dp数组的初始化。因为单独一个首元素也是一个回文子串,因此,我们令首元素尾1即dp[0]=1
4.dp数组的遍历顺序,我们可以从前向后遍历。

代码
class Solution {
public:
bool huiwen(string &s,int start,int end)
{
    while(start<end)
    {
        if(s[start]!=s[end]) return false;
        start++;
        end--;
    }
    return true;
}
    int countSubstrings(string s) {
        vector<int>dp(s.size(),1);
        if(dp.size()<=1) return dp.size();
        for(int ii =1;ii<s.size();ii++)
        {
            int temp=0;
            for(int jj =0;jj<=ii;jj++)
                if(huiwen(s,jj,ii))
                    temp++;
            dp[ii] = dp[ii-1]+temp;
        }
        return dp[s.size()-1];
    }
};

优化动态规划

上述暴力解法,时间复杂度尾O(n^3),我们可以用空间换取时间。
如果我们提前知道s[ii+1]到s[jj-1]是回文的,那么只要s[ii]和s[jj]相等,那么从s[ii]到s[jj]就也是回文的,因此我们可以用dp数组来实现这样的记录。
定义dp[ii][jj]为bool类型的二维数组,用来记录从ii到jj是否是回文的。对于dp[ii][jj],有四种情况,
如果s[ii]!=s[jj]那么我们直接让dp[ii][jj]=false,既然头与尾都不相等,那么一定不是回文的。
如果s[ii]==s[jj],并且ii=jj,即这个字符串只有一个字符那么他一定是回文的。
如果s[ii]==s[jj],并且两个元素紧邻,相邻的两个相同元素也一定是回文的。
如果s[ii]==s[jj],但是这个字符串的长度大于1,那么我们需要dp[ii+1][jj]的信息,如果dp[ii+1][jj]==true,那么说明从ii+1到jj-1是回文的,那么从ii到jj也是回文的。如果dp[ii+1][jj]==false,那么就代表着中间元素不是回文的,原字符串也不是回文的。
在第三种情况中,我们使用到了下一行的信息,因此,我们不能从第一行往后遍历,因为后面信息没有更新的话,会导致前面的元素不准确。
因此,我们需要从最后一行进行遍历,每一行从前向后遍历。

并且在遍历过程中,每遇到一次回文子串,我们都让结果进行加1,这样最后就不用再额外统计回文子串个数了。

动态规划四部曲。
1.dp数组含义。用来记录从ii到jj是否是回文的
2.dp数组的推导公式。

                if(s[jj] == s[ii])
                {
                    if((jj-ii)<=1) 
                    {
                        dp[ii][jj] =true;
                        result++;
                    }
                else
                    {
                        if(dp[ii+1][jj-1])
                        {
                            dp[ii][jj] = true;
                            result++;
                        }
                    }
                }

3.dp数组的初始化,显然,我们没有记录false的情况,因此都初始化为false即可。
4.遍历顺序,我们对每个首元素从后向前进行遍历,尾元素从前向后进行遍历。

代码

class Solution {
public:
    int countSubstrings(string s) {
        vector<vector<bool>>dp(s.size(),vector<bool>(s.size(),false));
        int result = 0;
        for(int ii =s.size()-1;ii>=0;ii--)
        {
            for(int jj = ii;jj<s.size();jj++)
            {
                if(s[jj] == s[ii])
                {
                    if((jj-ii)<=1) 
                    {
                        dp[ii][jj] =true;
                        result++;
                    }
                else
                    {
                        if(dp[ii+1][jj-1])
                        {
                            dp[ii][jj] = true;
                            result++;
                        }
                    }
                }
            }
        }
        return result;
    }
};

516.最长回文子序列

与上一题不一样的是,本题求得是子序列,也就是说所要求的子串不一定要连续。
因此,我们需要修改一下思路。
相对于连续子串,如果是子序列的话,我们一般是定义dp[ii][jj]为以s[ii]开始以s[jj]为结尾…。
对于此题,我们就定义dp[ii][jj]为以s[ii]开始以s[jj]为结尾,存在的最长回文子序列。

因此,如果首元素与尾元素相等,
1.如果ii==jj,那么相当于只有一个字符,对此,我们让这个dp[ii][jj] = 1;
2.如果ii == jj+1,那么说明有两个字符,我们让dp[ii][jj] = 2;
3.如果ii-jj>1,那么说明有两个以上的字符,因此,如果去掉首元素和尾元素,相当于最长回文子序列的个数就变成了dp[ii+1][jj-1],于是我们只需要在这个基础加上首尾元素即可。dp[ii][jj] = dp[ii + 1][jj - 1] + 2;
但是如果首元素和尾元素不相等,就相当于,如果我们要找最长的回文子序列,我们只能从舍弃首元素或者舍弃尾元素中取最大的,也就是说
dp[ii][jj] = max(dp[ii+1][jj],max(dp[ii][jj - 1],dp[ii+1][jj-1]));

最后我们返回dp[0][dp[0].size()-1]即可,因为这个元素表示从下标0到下标最大值所包含的最长回文子序列。

动态规划四部曲。
1.dp数组的含义。dp[ii][jj]为以s[ii]开始以s[jj]为结尾,存在的最长回文子序列。
2.dp数组的推导公式。

if (s[ii] == s[jj])
            {
                if ((jj - ii) == 0)
                {
                    dp[ii][jj]=1;
                }
                else if (jj == ii + 1)
                {
                    dp[ii][jj]=2;
                }
                else
                {
                    dp[ii][jj] = dp[ii + 1][jj - 1] + 2;
                }
            }
            else
                dp[ii][jj] = max(dp[ii+1][jj],max(dp[ii][jj - 1],dp[ii+1][jj-1]));

3.dp数组的初始化。显然,我们可以初始化这个数组为0,但是无所谓,我们遍历元素时,如果只有一个字符,我们直接令其为1,如果有两个字符,我们令其为2,因此没有影响。
4.dp数组的遍历顺序,首元素从尾开始遍历,尾元素从前向后进行遍历。

代码

class Solution {
public:
    int longestPalindromeSubseq(string s) {
vector<vector<int>>dp(s.size(), vector<int>(s.size(), 0));
    int result = 0;
    for (int ii = s.size() - 1; ii >= 0; ii--)
    {
        for (int jj = ii; jj < s.size(); jj++)
        {
            if (s[ii] == s[jj])
            {
                if ((jj - ii) == 0)
                {
                    dp[ii][jj]=1;
                }
                else if (jj == ii + 1)
                {
                    dp[ii][jj]=2;
                }
                else
                {
                    dp[ii][jj] = dp[ii + 1][jj - 1] + 2;
                }
            }
            else
                dp[ii][jj] = max(dp[ii+1][jj],max(dp[ii][jj - 1],dp[ii+1][jj-1]));
        }
    }
    return dp[0][dp[0].size()-1];
    }
};

你可能感兴趣的:(算法,leetcode,动态规划)