序列DP总结 (子序列问题,编辑距离,回文子串)

        DP问题没见过很难理解,但是模型也很多,写题的时候可以多套用之前模型的思路。这里结合AcWing闫总跟代码随想录卡哥的思想去理解序列DP。

子序列问题

   不连续子序列

      最长上升子序列

        题目大意:给定一个数组,找出其中最长严格递增的子序列长度。

        4557. 最长上升子序列 - AcWing题库

        力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

        最基本的模型,需要理解后把板子记下来。

        1.dp数组定义:所有以i结尾的上升子序列的集合。性质:最长(最大max)

        2.递推公式分析:看i之前的数,有0 ~ i-1个数,可以以此为划分依据。但要满足上升子序列的要求还需要满足a[j] < a[i] (j属于[0,i-1])。如果满足条件 f[ i ] = f [ j ] +1。

        核心代码:两重循环,第一重枚举所有i,第二重枚举i之前的数,也就是0 ~ i - 1的数

    for(int i =0;i<n;i++)
    {
        dp[i] = 1; //相当于初始化操作,只有i一个数的时候 最长上升子序列是本身,为1
        for(int j =0;j<i;j++)  //j不能等于i
            if(a[j]<a[i])
                dp[i] = max(dp[i],dp[j]+1);
    }

        3.dp数组初始化:当以i结尾的数只有i一个数的时候,f[i] = 1

        4.遍历顺序:从前往后遍历即可

        5. 打印dp数组:这里需要注意,dp[i] 的含义是以i结尾的最长上升子序列,但dp[i]不一定是所有中最长的,所以最后循环一边f[0]到f[n-1]取最大值就可以了。

code:

#include<iostream>
using namespace std;

const int N = 1005;
int a[N];
int dp[N];
int main()
{
    int n;
    cin>>n;
    for(int i = 0;i<n;i++) cin>>a[i];
    
    for(int i =0;i<n;i++)
    {
        dp[i] = 1; //相当于初始化操作,只有i一个数的时候 最长上升子序列是本身,为1
        for(int j =0;j<i;j++)  //j不能等于i
            if(a[j]<a[i])
                dp[i] = max(dp[i],dp[j]+1);
    }
    int ans = -1;
    for(int i = 0;i<n;i++) ans = max(ans,dp[i]);
    cout<<ans<<endl;
    return 0;
}

习题:482. 合唱队形 - AcWing题库

最长公共子序列

        题目大意:给定两个序列,求既是序列a的子序列和b的子序列的字符串最长长度。

        力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

        1.dp数组含义:dp[i][j] 表示所有在第一个序列的前i个字母中出现,且在第二个序列的前j个字母中出现的子序列的集合。性质:长度最大。

        2.递推公式分析:看最后一个元素,考虑第i个字母以及第j个字母是否满足条件。

 这里其实01和10的情况包括了00的情况,但求的是最大值,所有对结果没有影响的。

dp[i-1][j-1]选到是由要求的:a[ i ] == b[ j ] 只有相同了才可以

核心代码:

for(int i = 1;i<n;i++)
            for(int j = 1;j<m;j++)
            {
                if(text1[i]==text2[j])
                    f[i][j] = max(f[i][j],f[i-1][j-1]+1);
                else
                     f[i][j] = max(f[i-1][j],f[i][j-1]);
            }

        3.初始化:因为出现了i-1跟j-1,而i,j是从1开始的,所以要考虑0的情况。这里f[0][0]表示a,b都是空序列的情况,那自然f[0][0] = 0其他的f[i][j]取值只要不影响max判断即可,,所以全初始化为0即可。

        4.遍历顺序:

画图可知要先用到i-1,j-1的情况,所以从上往下从左往右遍历即可。

 code:这里是力扣上面的代码,我喜欢从输入的下标都从1开始,所以加了点初始化操作

class Solution {
public:
    int f[1005][1005];
    int longestCommonSubsequence(string text1, string text2) {
        text1.insert(text1.begin(),0);
        text2.insert(text2.begin(),0);
        int n = text1.size();
        int m = text2.size();
        for(int i = 1;i<n;i++)
            for(int j = 1;j<m;j++)
            {
                if(text1[i]==text2[j])
                    f[i][j] = max(f[i][j],f[i-1][j-1]+1);
                else
                     f[i][j] = max(f[i-1][j],f[i][j-1]);
            }
    return f[n-1][m-1];
    }
};

 不相交的线

        直线不能相交,这就是说明在字符串A 中 找到⼀个与字符串 B 相同的子序列,且这个子序列不能改变相对 顺序,只要相对顺序不改变,链接相同数字的直线就不会相交。
        所以其实就是最长公共子序列问题,思路一样,代码直接给出。

        力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

     code:

class Solution {
public:
    int f[505][505];
    int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
        nums1.insert(nums1.begin(),0);
        nums2.insert(nums2.begin(),0);
        int n = nums1.size();
        int m = nums2.size();
        for(int i = 1;i<n;i++)
            for(int j = 1;j<m;j++)
            {
                f[i][j] = max(f[i-1][j],f[i][j-1]);
                if(nums1[i] == nums2[j])
                    f[i][j] = max(f[i][j],f[i-1][j-1]+1);
            }
        return f[n-1][m-1];
    }
};

连续子序列

        最长连续递增序列

        题目大意:给定一个数组,求其中最长的连续递增序列。

        与最长上升子序列的区别:子序列可以是不连续的。

        与最长上升子序列很像,区别在于递推公式。

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

        1.dp数组含义:所以以i结尾的连续子序列的长度的集合。性质:最大值max

        2.递推公式:还是看最后一个字母,如果a[i] < a[i+1]的话,f[i+1] = f[i] +1 。

        如何保证连续性呢?在最长上升子序列中,我们其实是枚举了很多的子序列,因为两次循环,第一次枚举结尾,第二次枚举确定结尾后有的子序列。要保证是连续的,就用一次循环,从头到尾遍历即可。

        3.初始化:f[i] = 1,表示如果只有自己一个元素的话长度就为1

        4.遍历顺序:从左向右

        code:同理,f[i]并不一定是我们要的最大值,只是以i结尾的最大值

class Solution {
public:
     int dp[10004];
    int findLengthOfLCIS(vector<int>& nums) {
        int n = nums.size();
        fill(dp,dp+n,1);
        for(int i = 0;i<n-1;i++)
            if(nums[i+1]>nums[i])
                dp[i+1] = dp[i] + 1;
        int ans = 0;
        for(int i = 0;i<n;i++) ans = max(ans,dp[i]);
        return ans;
    }
};

        最长重复子数组

        题目大意:给定两个数组,求两个数组中最长的公共子数组。注意:子数组是要求连续的

        1.dp数组含义:dp[i][j]表示a数组中以i结尾,b数组中j结尾的公共子数组长度。性质:Max

        2.确定递推公式:如果a[i] == b[j]的话,f[i][j] = max(f[i-1][j-1]+1,f[i][j]),如果不相等呢?不相等说明已经不可以构成两个相等的子数组了,直接赋0即可 f[i][j] = 0

        3.初始化:f[0][0],两个空数组的公共子数组长度为0

        4.遍历顺序:两个数组从左往右即可

        这里有个疑问,为什么不按照之前最长公共子序列的定义方法呢?dp[i][j]表示a数组中从0-i,b数组中从0-j的公共子数组的最大值?因为无法保证连续性。但是结尾的定义可以。为什么?因为a[i]!=b[j]的话就会等于0了,0是什么?0是初始化的值。这就保证了不连续。如果按照0-i,0-j的定义,f[][]的值一直在上升,不会回到0,那按照定义岂不是都是公共子数组了?显示不是。

        code:

class Solution {
public:
    int f[1005][1005];
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        nums1.emplace(nums1.begin(),0);
        nums2.emplace(nums2.begin(),0);
        int n = nums1.size();
        int m = nums2.size();
        for(int i = 1;i<n;i++)
        {
           for(int j = 1 ;j<m;j++)
           {
               if(nums1[i] == nums2[j])
                    f[i][j] = max(f[i][j],f[i-1][j-1] +1);
                else 
                   f[i][j] = 0;
           }
        }
        int ans =0;
        for(int i =0;i<n;i++)
            for(int j = 0;j<m;j++)
                ans = max(ans,f[i][j]);
        return ans;

    }
};

        最大子序和

        题目大意:给定一段数组,求其中连续的子数组的和最大值

        1.dp数组定义:dp[i]表示以i结尾的子数组的和的集合。性质:和的最大值

        2.递推公式:看最后一个数即可。如果已经循环到a[i],之前的最大值f[i-1]加上a[i]后还没有f[i-1]大,那么f[i]就应该回到0,加上a[i],这样保证连续性。 f[i] = max(f[i-1]+a[i],a[i]);

        3.初始化: f[0]表示一个数都没有的时候的值就为0

        4.遍历顺序:从左向右

        code:

class Solution {
public:
    int f[100005];
    int maxSubArray(vector<int>& nums) {
        nums.insert(nums.begin(),0);
        int n = nums.size();
        for(int i = 1;i<n;i++)
                f[i] = max(f[i-1] + nums[i],nums[i]);
        int ans = INT_MIN;
        for(int i = 1;i<n;i++) ans = max(f[i],ans);
        return ans;
    }
};

总结:由于是连续的子序列,如何体现连续性呢?首先dp数组定义:一般都是以最后一个数作定义。dp数组不能一直增加,如果不满足状态就应该从初始化开始再变化。最后取一个最值。

编辑距离问题

判断子序列

        力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

        题目大意:给定连个子序列a,b。问a是否是b的一个子序列。

        其实是一个很经典的双指针问题,都知道是双指针问题,但是如何证明正确性是有难度的。这里介绍动态规划的方法。

        1. 定义dp数组:dp[i][j]:a中前i个字符,b中前j个字符 中的公共字符长度的集合。性质:max

        2.递推公式: 分析a,b中最后一个字符的情况,分为两类:

        ① a【i】 == b【j】 这种的话 f[ i ][ j ] = max(f[ i ][ j ] , f[ i -1][j - 1] + 1)

        ② a【i】!=b【j】 这种就要考虑能不能b删除一个字符来满足情况:f[ i ][ j ] = max(f[ i ][ j ],f[ i ][ j - 1]) 。 f[ i ][ j -1]表示考虑a中前i个,b中前j-1个,也就是b中删除一个的情况。

        3.初始化: f[ 0 ][ 0 ] = 0。a为空,b为空的时候,没有相同的部分。

        4.遍历顺序,从左往右。

        code: 最后只要判断一下f[n-1][m-1]是否与n-1的个数相等即可

class Solution {
public:
    int f[105][1005];
    bool isSubsequence(string s, string t) {
        s.insert(s.begin(),'0');
        t.insert(t.begin(),'0');
        int n = s.size();
        int m = t.size();
        for(int i = 1;i<n;i++)
            for(int j = 1;j<m;j++)
            {
                if(s[i] == t[j])
                    f[i][j] = max(f[i-1][j-1]+1,f[i][j]) ;
                else f[i][j] = max(f[i][j],f[i][j-1]);
            }
        return f[n-1][m-1] == n-1;
    }
};

不同的子序列

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

题目大意:给定字符串s,t 求 t在s的子序列中出现过几次。

换句话说,s中有几种删除字母的方式可以得到t,就出现过几次。

1. dp数组定义: dp[i][j] s中以i结尾,t中以j结尾的序列,s可以通过删除字母的方式可以得到t的方法集合。 性质:数目(count)。 注意这里是删除方法的集合。

2. 递推公式:分析最后一个字母情况。分为两种情况:

        ① 如果s[i] == t[j] f[i][j] = f[i-1][j-1] + f[i-1][j]。 这里f[i-1][j-1]都好理解,直接从i,j上一个状态加过来就行了,为什么还要加一遍f[i-1][j]呢 例如 “abcc”与“abc” 当最后一个字母相同的时候,“abcc”可以删除第一个c也可以删除第二个c去匹配"abc",所以要加上。

        ②如果s[i]!=t[j],这个时候就要考虑下删除下i,即不选i,看下s[i-1]与t[j]能否匹配了  f[ i ][ j ] = f[ i - 1 ][ j ]

3.初始化:这里要考虑下了,f[i][0] 一定都为1,为什么?s有i位,t为空串,只有一种方法从s到t,就是全删完。f[0][j]一定都为0,表示s为空串,不可能删除得到t了。那f[0][0]呢?应该为1,删除0个元素从空串到空串。

4.遍历顺序:从左往右。

code:

class Solution {
public:
    uint64_t f[1005][1005];
    int numDistinct(string s, string t) {
        s.insert(s.begin(),'1');
        t.insert(t.begin(),'1');
        int n = s.size();
        int m = t.size();
        for(int i = 0;i<n;i++) f[i][0] = 1;
        for(int j = 1;j<m;j++) f[0][j] = 0;
        for(int i = 1;i<n;i++)
            for(int j = 1;j<m;j++)
            {
              if(s[i] == t[j]) f[i][j] = f[i-1][j] + f[i-1][j-1];
              else f[i][j] = f[i-1][j];
            }
        return f[n-1][m-1];
    }
};

那 判断子序列 中是否也要考虑最后一个字符相等的情况下,但是可以选择删除s的最后一个字符而使s变为t呢?因为求的是最大值,其实加上也可以 f[i][j] = max({f[i-1][j-1]+1,f[i][j],f[i][j-1]}) 也是对的。

两个字符串的删除操作

    题目大意:给定两个字符串,在都可以删除字母的情况下,问最少几步可以让两个字符串相等。

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

        1.dp数组定义:dp[i][j]:s串中0-i,t串0-j中通过删除使两个字符串相等的步数的集合。性质:最小步数(Min)

        2.递推公式:按照最后一个字母分析:

        ① 如果最后一个字符相等: s[ i ] == t[ j ] 那说明不用删除两串中的一个字符就能相等,所以最小值从 f[i-1][j-1]中选取即可。 f[i][j] = min(f[i][j],f[i-1][j-1]);

        ②如果最后一个字符不相等,那需要删除一个字符让两者相等,可以选择删s[i]也可以删除t[j],不论删除哪个最小步数都要加一。 f[i][j] = min(f[i-1][j],f[i][j-1])+1;

        3.初始化数组:f[i][0] 表示s串有i个字符,t串只有0个字符,要想两者最后相等只有把s删完,所以步数就为i。同理f[0][i] = i。由于求的是最小值,所以一开始的初始化要不影响后面的取min操作,所以其余全部初始化为正无穷即可。

        4.遍历顺序:从左往右。

        code:

class Solution {
public:
    int f[505][505];
    int minDistance(string w1, string w2) {
        w1.insert(w1.begin(),'0');
        w2.insert(w2.begin(),'0');
        int n = w1.size(),m = w2.size();
        memset(f,0x3f,sizeof f);
        for(int i = 0;i<n;i++) f[i][0] = i;
        for(int i = 0;i<m;i++) f[0][i] = i;

        for(int i = 1;i<n;i++)
            for(int j = 1;j<m;j++)
            {
                if(w1[i]==w2[j])
                    f[i][j] = min(f[i][j],f[i-1][j-1]);
                else 
                    f[i][j] = min(f[i-1][j]+1,f[i][j-1]+1);
            }
        return f[n-1][m-1];
    }
};

        编辑距离

        题目大意:给定两个字符串s,t。对s可以有三种操作:删一个字母,加一个字母,改一个字母。求从s变为t的最小操作数。

        力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

        1.dp数组分析:dp[i][j]表示从s串中0-i,t串中0-j通过编辑操作使s变为t的操作数的集合。性质:最小操作数(min)

        2.递推公式分析:考虑最后一个字符的三种操作:

        ①如果 s[ i ] == t[ j ] 那么最小值就是与原值取min即可。 f[ i ][ j ] = min(f[ i ] [j ],f[ i -1][ j - 1]);

        ②如果 s[ i ] != t[ j ] 那么就可以有三种操作了:

                a.删s中的一个字母:说明s的前i-1个字母与t前j个字母相等,那就是f[i-1][j] + 1;

                b.给s加一个字母:说明加之前s前i个与t的前j-1个字母相等,那就是f[i][j-1] + 1;(其实也是相当于给t中删一个字母)

                c.改s的最后一个字母:只差最后一个字母相同,说明s的前i-1个字母与t中的前j-1个字母是相等的。那就是f[i-1][j-1]+1

                综上所述:那就是s[ i ] != t[ j ]的情况下有三种情况,取最小即可:f[i][j] = min(f[i-1][j],f[i][j-1],f[i-1][j-1])+1;

          3.初始化操作:由于取min,为了不干扰取min操作,一开始全部赋值为正无穷即可。由于要用到f[i][j-1]与f[i-1][j],那就要考虑f[0][i]与f[i][0]了。

        f[i][0]:表示s中子串长度为i,t为空串。根据dp数组分析,需要变成空串需要i步。

        f[0][j]:   表示s为空串,t为长度为j的串。我们需要j步才可以把s变成t。

        4.遍历顺序:从左往右即可。

        code:

class Solution {
public:
     int f[505][505];
    int minDistance(string w1, string w2) {
        w1.insert(w1.begin(),'0');
        w2.insert(w2.begin(),'0');
        int n = w1.size(),m = w2.size();
        memset(f,0x3f,sizeof f);
        for(int i = 0;i<n;i++) f[i][0] = i;
        for(int j = 0;j<m;j++) f[0][j] = j;

        for(int i =1;i<n;i++)
            for(int j = 1;j<m;j++)
            {
                if(w1[i] == w2[j])
                    f[i][j] = min(f[i-1][j-1],f[i][j]); 
                else
                    f[i][j] = min({f[i-1][j],f[i][j-1],f[i-1][j-1]})+1;
            }
        return f[n-1][m-1];
    }
};

回文子串问题

        回文子串的问题一开始很难想,但是感觉模板就是那样的,很好套用。

回文子串

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

        力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

        1.dp数组定义:dp[ i ][ j ] 表示一个字符串中第i位到第j位是否是回文子串的集合。性质:True/False。

        2.递推公式分析:

        ①   如果开头的字母与结尾的字母相同:即s[i+1] == s[j-1]:

                a. 如果i与j是相等的: 例如:“a” 这个时候说明是一个回文子串。

                b.如果j = i + 1:例如 “aa” 这个时候也是一个回文子串。

                c.如果j>i+1:例如 “abba” 和 “abca”这个时候是否是一个回文子串就要判断一下f[i-1][j+1]是否是True,如果是True,那就是回文子串,如果不是,那就不是回文子串。

        ② 如果开头与结尾的字母不相同: 即s[i+1] != s[j-1]:说明肯定不是回文子串,直接False即可。

        综上所述: 若 s[i + 1] == s[ j - 1] 看j与i的关系继续判断,若不相等,则直接为False

        3. 初始化操作: f[0][0]表示空字符串,那肯定不是回文字符串,所以全初始化为0即可。

        4.遍历顺序: 回文子串的递推公式感觉是有规律的。因为是用到f[i + 1][ j - 1],但是这个数是在f[i][j]的左下角的,也就是说i要从下往上遍历,j还是从左向右遍历的。那j还是从0开始遍历吗?

        其实这里我感觉i和j相当于枚举了左右端点,i是左端点,j是右端点。所以j是从i开始循环的,一直到size()-1结束。

        code:这里如果是True,则说明这一段是回文子串,ans++即可。由于枚举了所有的左右端点,所以是不重不漏的。

class Solution {
public:
    bool f[1005][1005];
    int ans;
    int countSubstrings(string s) {
        int n = s.size();
        for(int i = n-1;i>=0;i--)
            for(int j = i;j<n;j++)
            {
                if(s[i] == s[j])
                {
                    if(j-i<=1) 
                        f[i][j] = 1,ans++;
                    else 
                        if(f[i+1][j-1]) ans++,f[i][j] = 1;
                }
                else f[i][j] =0;
            }
        return ans;
    }
};

 最长回文子串

        题目大意:找出一个回文串中最长的子串。

        力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

        1.dp数组定义: dp[i][j]表示一个字符串中从i到j是否是回文子串的集合。性质:True/False

        2.递推公式分析:与上题相同。

        3.初始化:与上题相同。

        4.遍历顺序:与上题相同。

        code: 不同的是要返回最长子串。如何记录呢?在每次判断后再加个判断:如果这一段是回文子串的话,且这次的回文子串的长度比上一次要长的话,我们就更新一下最长子串。 由于我们记录了左右端点的下标,长度可以用r-l+1表示出来。同理我们如果知道了区间长度len,和左端点l,右端点也可以用长度跟l表示出来的: r = len+l-1。区间dp的时候要这样考虑的。

class Solution {
public:
    bool f[1005][1005];
    string longestPalindrome(string s) {
        int n  = s.size();
        if(n==0||n==1) return s;
        int maxlen = 0,lidx = 0;
        for(int i = n-1;i>=0;i--)
            for(int j = i;j<n;j++)
        {
            if(s[i] == s[j])
            {
                if(j-i<=1) f[i][j] = 1;
                else 
                    if(f[i+1][j-1]) f[i][j] = 1;
            }
            else
                f[i+1][j-1] = 0;
            if(f[i][j] == 1 && j-i+1>maxlen)
            {
                lidx = i;
                maxlen = j-i+1;
            }
        }
        return s.substr(lidx,maxlen);
    }
};

          密码脱落

        1222. 密码脱落 - AcWing题库

        题目大意:给定一个字符串,问最少添加几个字符可以让它变为回文串

        1.dp数组定义:dp[i][j]:表示一个串中从i到j的子串通过添加字符的方式变成回文串的步数集合。性质:最小步数(Min)

        2.递推公式分析: 看最后一个字符:s[i]与s[j]

          ①若s[ i ] == s[ j ]:

                a.若j-i<=1:说明是"aa"或者是"a"的情况,那f[i][j] = 0即可

                b.若j-i>1,那就要看s[i+1]与s[j-1]的情况了,与f[i+1][j-1]取Min

           ②若s[ i ] != s[ j ]: 若原来是回文串,那就要添加字符了,可以添加在头或者是尾

                        f[ i ][ j ] = min(f[ i + 1 ][ j ],f[ i ][ j - 1 ])+1,+1表示操作数要加一了。

        3.初始化:全部初始化为正无穷即可。

        4.遍历顺序:老规矩:i从大到小,j从i到n。

        code:

#include<iostream>
#include <cstring>
using namespace std;

int f[1005][1005];

int main()
{
    string s;
    cin>>s;
    int n = s.size();
    
    memset(f,0x3f,sizeof f);
    
    for(int i = n-1;i> -1;i--)
    {
        for(int j = i;j<n;j++)
        {
            if(s[i] == s[j])
            {
                if(j-i <=1 ) f[i][j] = 0;
                else f[i][j] = min(f[i][j],f[i+1][j-1]);
            }
            else
                f[i][j] = min(f[i+1][j],f[i][j-1])+1;
        }
    }
    cout<<f[0][n-1]<<endl;
    
    return 0;
}

最长回文子序列

 题目大意:给应一个字符串,求对于的回文子序列最大值。

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

        1.dp数组定义:dp[i][j]:表示从串从i到j回文子序列的长度的集合。性质:Max

        2.递推公式分析:还是看s[i]与s[j]:

        ①s[i] == s[j]:

                a. i == j 说明是一个字符,肯定是回文的。f[i][j] = 1;

                b. j==i+1 说明是"aa",f[ i ][ j ] =2; 

                c. i != j  说明要在之前的f[i+1][j-1]的基础上长度加上2 ,f[i][j] = f[i+1][j-1] +2;

        ②s[i] != s[j]:

                这就要看一下f[i+1][j]与f[i][j-1]谁大了: f [ i ][ j ] = max(f[ i + 1 ][ j ],f[ i ][ j - 1 ]);

        3.初始化:求最大值,初始化为0即可。

        4.遍历顺序:老规矩:i从大到小,j从i到n。

        code

        

class Solution {
public:
    int f[1005][1005];
    int longestPalindromeSubseq(string s) {
        int n = s.size();
        for(int i = n-1;i>=0;i--)
            for(int j = i;j<n;j++)
            {
                if(s[i] == s[j])
                {
                    if(j==i)
                        f[i][j] = 1;
                    else if(j == i+1)
                        f[i][j] = 2;
                    else    
                        f[i][j] = f[i+1][j-1] + 2;
                }
                else
                    f[i][j] = max(f[i+1][j],f[i][j-1]);
            }
                return f[0][n-1];
    }

};

至此,暑假写的一些序列dp问题写完了,还有很多dp模型:区间dp,背包dp等等,有时间一定要记录。算法初学者,不喜勿喷。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值