leetcode动态规划之子序列、子串问题

leetcode动态规划之子序列、子串问题

  • 动态规划
  • 一、斐波那契数列
    • 1、迭代写法(dp数组)
    • leetcode70爬台阶
  • 二、leetcode 322零钱兑换
    • 1、备忘录法(递归)
    • 2、dp数组法(自底向上)迭代版本
  • 三、1143最长公共子序列
    • leetcode 72编辑距离
    • leetcode 712. 两个字符串的最小ASCII删除和
  • 四、 一维背包子序列问题
    • leetcode 300最长递增子序列
      • leetcode 334最长递增三元组(思路同最长递增子序列)
    • leetcode 53最大字串和
  • 五 回文子串/子序列问题
    • leetcode 516最长回文子序列
    • leetcode647 回文子串
    • leetcode5 最长回文子串
    • 131分割回文串

动态规划

一、斐波那契数列

1、迭代写法(dp数组)

// An highlighted block
class Solution {
public:
    int fib(int n) {
        vector<int> dp(n+1,0);//vector要记得初始化
        if(n<1)
        return 0;
        if(n==1||n==2)
        return 1;
        for(int i=1;i<=n;i++)
        {
            if(i==1||i==2)
            dp[i]=1;
            else
            dp[i]=(dp[i-1]+dp[i-2])% 1000000007;//注意取余数
        }
       return dp[n];
    }
};

套路来自:https://labuladong.gitbook.io/algo/

  • 为什么要取余
    -大数阶乘,大数的排列组合等,一般都要求将输出结果对1000000007取模
    为什么总是1000000007呢?

大数求余原因:大数越界

大数越界:随着n增大,f(n)会超过Int32甚至Int64的取值范围,导致最终的返回值错误。
1、 当一个问题只对答案的正确性有要求,而不在乎答案的数值,可能会需要将取值很大的数通过求余变小。

2.int32位取值范围是-2147483648~2147483647,1000000007 是最小的十位质数。模1000000007,可以保证值永远在int的范围内。

3.int64位的最大值为2^63-1,对于1000000007来说它的平方不会在int64中溢出 所以在大数相乘的时候,因为(a∗b)%c=((a%c)∗(b%c))%c,所以相乘时两边都对1000000007取模,再保存在int64里面不会溢出

原文链接:https://blog.csdn.net/weixin_46359814/article/details/110109550

leetcode70爬台阶

  • 相似点:状态相加问题
  • 易错点:条件判断一定要能覆盖变量全部取值范围!
    有条件判断时一定要看一看条件判断是不是在变量范围内都成立,否则会出现越界问题,需要补全条件判断。
class Solution {
public:
    int climbStairs(int n) {
//选择 爬1、2台阶
//状态 剩余台阶数
// base n=0 return 0 n=1 return 1 
vector<int> dp(n+1,0);
if(n==0)
return 0;
if(n==1)
return 1;
if(n==2)
return 2;
dp[0]=0;
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++)
dp[i]=dp[i-1]+dp[i-2];

   
    return dp[n]; }
};

二、leetcode 322零钱兑换

1、备忘录法(递归)

  • 最值问题

  • 易错点:变量可能有多种状态时,要将每种状态对之后的影响都考虑到
    Eg: amount:0(正常结束)
    amount: -1(问题不成立)------->res的此次判断无效
    res: min(find(amount-coin) in coins)(res作为中间变量正常改变),返回res
    res:INT_MAX(初始状态)--------->无有效子问题,返回时应返回-1

  • vector初始化
    1、一维

     vector< int > A(n,0);
     vector< int > B;
     	B.resize(n,0);
    

    2、二维

     		vector > v(2, vector(4,1));
     		vector > v(2);   //2行
     		// v.resize(2);
     		for(int i=0;i

https://blog.csdn.net/DreamLike_zzg/article/details/86760751

  • 动态数组

     				    //申请空间
     int** a2 = new int*[rows];
     for(int i=0;i

    https://blog.csdn.net/bqw18744018044/article/details/81665898
    代码

class Solution {
    
public:
  vector<int> memo;
    int find(vector<int>& coins, int amount) {
        if(amount==0)
        return 0;//base case
        if(amount<0)
        return -1;//base case
        //状态min(find(amount-coin) in coins)
        int res=INT_MAX;
        if(memo[amount]!=-3)
        return memo[amount];
        for(int i =0;i<coins.size();i++)
        {
            int sub=find(coins,amount-coins[i]);//子问题中的硬币数量;
            if(sub==-1)//关键:要排除子问题不成立的情况,此时res=上一次的res------>有可能有res依旧是int_max情况,输出时要记得处理
            continue;
            res=min(res,sub+1);
           

        }
         memo[amount]=res!=INT_MAX?res:-1;//边界条件
        return memo[amount];

    }

int coinChange(vector<int>& coins, int amount)
{
    if(amount<1)
    return 0;//边界条件
    memo.resize(amount+1,-3);//初始化备忘录
    return find(coins,amount);
   

}
    
};

2、dp数组法(自底向上)迭代版本

这个好理解多了,也没有很多的边界条件判断,推荐。

class Solution {
    
public:
int coinChange(vector<int>& coins, int amount)
{
vector<int> dp(amount+1,amount+1);
dp[0]=0;
for(int i=1;i<=amount;i++)
{
    for(int coin:coins)
    {
        if(i-coin<0)
        continue;
        dp[i]=min(dp[i],dp[i-coin]+1);
    }
}
return dp[amount]==amount+1?-1:dp[amount];
   

}
    
};

三、1143最长公共子序列

状态:字符串 两字符串一起对比下一个
字符不等 s1对比下一个or s2对比下一个
初始条件 0(没有公共子序列)
1、备忘录递归
c++超时了,java可以过

class Solution {
public:
vector<vector<int> > memo;
    int longestCommonSubsequence(string text1, string text2) {
memo.resize(text1.length());
for(int i=0;i<text1.length();i++)
{
    memo[i].resize(text2.length());
    for(int j=0;j<memo[i].size();j++){
 									memo[i][j]=-1; 
}
}
        return dp(text1,0,text2,0);

    }
    int dp(string s1,int i,string s2,int j)
    {
        if(i==s1.length()||j==s2.length())//结束条件:空串(注意不要指在最后一个字符就结束了,一定到空串)
        return 0;
        if(memo[i][j]!=-1)
        {
            return memo[i][j];}
        if(s1[i]==s2[j])//base
        {memo[i][j]= 1+dp(s1,i+1,s2,j+1);
        }
        else
        memo[i][j]= max({dp(s1,i+1,s2,j),dp(s1,i,s2,j+1)}) ;
return memo[i][j];
        
    }
};

2、自底向上,二维背包


class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int**memo=new int*[text1.length()+1];
        for(int i=0;i<=text1.length();i++)
        {
            memo[i]=new int[text2.length()+1];
            for(int j=0;j<=text2.length();j++)
            memo[i][j]=0;
        }
       
       for(int i=1;i<=text1.length();i++)
       {
           for(int j=1;j<=text2.length();j++)
           {
               if(text1[i-1]==text2[j-1])
               memo[i][j]=1+memo[i-1][j-1];
               else
               memo[i][j]=max({memo[i-1][j],memo[i][j-1]});

           }
       }
       return memo[text1.length()][text2.length()];}


    };

leetcode 72编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/edit-distance
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

选择:删除,改变,插入、不变
状态转移:dp[i][j]=min{dp[选择]}
初始化:一方长度为0时,最小编辑次数应为另一方长度。

class Solution {
public:
    int minDistance(string word1, string word2) {
        int **dp=new int*[word1.length()+1];
        for(int i=0;i<=word1.length();i++)
        {
            dp[i]=new int[word2.length()+1];
            for(int j=0;j<=word2.length();j++)
            {
                dp[i][0]=i;
                dp[0][j]=j;
            }
        }
        for(int i=1;i<=word1.length();i++)
        {
            for(int j=1;j<=word2.length();j++)
            {
                if(word1[i-1]==word2[j-1])
                dp[i][j]=dp[i-1][j-1];
                else{
                    dp[i][j]=min({dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+1});
                }
            }
        }
            
            return dp[word1.length()][word2.length()];
    }
};

leetcode 712. 两个字符串的最小ASCII删除和

给定两个字符串s1, s2,找到使两个字符串相等所需删除字符的ASCII值的最小和。

1、自底向上(按照题目要求进行)

class Solution {
public:
    int minimumDeleteSum(string s1, string s2) {
        int**dp=new int*[s1.length()+1];
        for(int i=0;i<=s1.length();i++)
        {
            dp[i]=new int[s2.length()+1];
            
        }
        //dp数组的初始化很重要
dp[0][0]=0;
        for (int i = 1; i <= s1.length(); ++i) {
            dp[i][0] = dp[i - 1][0] + int(s1[i - 1]);
        }

        for (int j = 1; j <= s2.length(); ++j) {
            dp[0][j] = dp[0][j - 1] + int(s2[j - 1]);
        }

        for(int i=1;i<=s1.length();i++)
        {
            for(int j=1;j<=s2.length();j++)
            {
                if(s1[i-1]==s2[j-1])
                dp[i][j]=dp[i-1][j-1];
                else
                dp[i][j]=min({dp[i][j-1]+int(s2[j-1]),dp[i-1][j]+int(s1[i-1])});

            }
        }
        return dp[s1.length()][s2.length()];

    }
};

2、最长子序列解法
最小删除ascall码<=>寻找ascall码最大相同子序列

明天写。

四、 一维背包子序列问题

leetcode 300最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

这是一道一维dp问题,状态转移为dp[n]=max(dp[i] in i 初始化为dp[n]=1。

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> dp(nums.size(),1);//初始化,dp(i)定义为num[i]为结尾的最长子序列长度,所以至少是1;
        for(int i=0;i<nums.size();i++)
        {
            for(int j=0;j<i;j++)//注意:由于子序列要求不改变原序列的顺序,所以找小的要从前面找。
            {
                if(nums[j]<nums[i])
                {
                    dp[i]=max({dp[j]+1,dp[i]});
                }
              
            }
        }
        int res=1;
        for(int i=0;i<nums.size();i++)
        {
            res= max(res,dp[i]);

        }
        return res;


    }
};

leetcode 334最长递增三元组(思路同最长递增子序列)

给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。
如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/increasing-triplet-subsequence

如果最长递增子序列>=3则返回true

class Solution {
public:
    bool increasingTriplet(vector<int>& nums) {
        vector<int> dp(nums.size(),1);
        for(int i=0;i<nums.size();i++)
        {
            for(int j=0;j<i;j++)
            {
                if(nums[j]<nums[i])
                dp[i]=max(dp[i],dp[j]+1);
            }
            if(dp[i]>=3)
            return true;
        }
        return false;
        

    }
};

leetcode 53最大字串和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

  • 和子序列问题思路相似dp[n]含义也相似,但由于是连续子串,因此只需比较第n个和n-1个就好,无需进行内部遍历。
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        //dp[n]含义以nums[n]为结尾的子数组的最大和。
        //状态转移:dp[n]=max{dp[n-1]+num[n],dp[n]},因为是连续的,所以不需要遍历 i in n 了
        //初始化:dp[n]=nums[n];
vector<int > dp(nums.size(),0);
for(int i=0;i<nums.size();i++)
dp[i]=nums[i];
int res=dp[0];
for(int i=1;i<nums.size();i++)
{
    dp[i]=max(dp[i-1]+nums[i],dp[i]);
    res=max(dp[i],res);
}
return res;

    }
};

五 回文子串/子序列问题

leetcode 516最长回文子序列

给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。

dp[i][j]:以i,j为首尾位置的回文子序列最大长度
状态:子序列首元素与尾元素位置
选择:i+1,j-1 (遍历方向为由下到上,由左到右),因为希望结束在dp[0][length-1]
初始化: i=j时子序列长度为1

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int**dp=new int*[s.length()];
        for(int i=0;i<s.length();i++)
        {dp[i]=new int[s.length()];
        for(int j=0;j<s.length();j++)
       { dp[i][j]=0;
        if(i==j)
        dp[i][j]=1;}
        }
        for(int i=s.length()-1;i>=0;i--)
        {
            for(int j=i+1;j<s.length();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.length()-1];

    }
 
};

leetcode647 回文子串

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

  • 回文子串题目要对奇数偶数有所判断,但由于j-i<=2时只要s[i]=s[j]就一定是回文子串,奇数和偶数得到统一,进行自底向上运算时就不需要区分奇数偶数了。
class Solution {
public:
    int countSubstrings(string s) {
vector<vector<bool>> dp(s.size(),vector<bool>(s.size(),false));
//dp[i][j]在这两个区间内的子串是否为回文子串
//初始化 i=j时是回文子串 bool=true
//状态转移:如果s[i]!=s[j]则进行下一次遍历,如果相等  dp[i][j]=dp[i+1][j-1]|(j-i<=2); //包含奇数偶数的情况
//状态转移方向:参考最长回文子序列
for(int i=0;i<s.length();i++)
{
    for(int j=0;j<s.length();j++)
    {
        if(i==j)
        dp[i][j]=true;
    }
}
int count =0;
for(int i=s.length()-1;i>=0;i--)
{
    for(int j=i+1; j<s.length();j++)
    {

        if(s[i]!=s[j])
        {
            continue;//为什么不是dp[i][j]=dp[i+1][j]|dp[i][j-1]??因为是计算回文子串的数量,是连续的,所以一旦断掉了就进行下一轮遍历了不用状态转移
        }
        dp[i][j]=dp[i+1][j-1]||(j-i<=2);

        }
        
    }
for(int i=0;i<s.length();i++)
{
    for(int j=0;j<s.length();j++)
    {     if (dp[i][j])
                count++;

    }
}          

    return count;
}
    };

leetcode5 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

方法一:参考回文子串题,将所有回文子串找出来后比大小。O(n^2)

class Solution {
public:
    string longestPalindrome(string s) {
vector<vector<bool>> dp(s.size(),vector<bool>(s.size(),false));
//dp[i][j]在这两个区间内的子串是否为回文子串
//初始化 i=j时是回文子串 bool=true
//状态转移:如果s[i]!=s[j]则进行下一次遍历,如果相等  dp[i][j]=dp[i+1][j-1]|(j-i<=2); //包含奇数偶数的情况
//状态转移方向:参考最长回文子序列
for(int i=0;i<s.length();i++)
{
    for(int j=0;j<s.length();j++)
    {
        if(i==j)
        dp[i][j]=true;
    }
}
int maxlen =1;
for(int i=s.length()-1;i>=0;i--)
{
    for(int j=i+1; j<s.length();j++)
    {

        if(s[i]!=s[j])
        {
            continue;//为什么不是dp[i][j]=dp[i+1][j]|dp[i][j-1]??因为是计算回文子串的数量,是连续的,所以一旦断掉了就进行下一轮遍历了不用状态转移
        }
        dp[i][j]=dp[i+1][j-1]||(j-i<=2);

        }
        
    }
    int begin=0;
for(int i=0;i<s.length();i++)
{
    for(int j=0;j<s.length();j++)
    {     if (dp[i][j])
                if(j-i+1>=maxlen)
                {maxlen=j-i+1;
                begin=i;}

    }
}          

    return s.substr(begin,maxlen);
}
};

方法二 最坏情况O(n^2),最好情况O(n)

class Solution {
public:
    string longestPalindrome(string s) {
        string res;
        for(int i=0;i<s.length();i++)
        {
            string s1=find(i,i,s);
            string s2=find(i,i+1,s);
            res=res.length()>s1.length()?res:s1;
            res=res.length()>s2.length()?res:s2;


        }
        return res;



    }
    string find(int left,int right,string s)//找到以l,r开始的最长回文子串
    {
        while(left>=0&&right <s.length()&&s[left]==s[right])//判断数组是否越界
        {
            left--;
            right++;
        }
        return s.substr(left+1,right-1-left);//substr的使用,数组越界的话会返回空串

    }
};

131分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。

  • 这个题解之后再写,主要是用回溯法(还没学)。
class Solution {
public:
vector<vector<string>> res;
    vector<string> path;//放已经回文的子串
    vector<vector<string>> partition(string s) {  

res.clear();
path.clear();
    backtrack(s,0);
    return res;
    }

            void backtrack(string &s,int start)
    {
        if(start>=s.size())
        {res.push_back(path);
        return;}
        for(int i=start;i<s.size();i++)
        {
            if(isPalindrome(s,start,i))
            {string str=s.substr(start,i-start+1);

            path.push_back(str);
            }
            else
            continue;   
            backtrack(s,i+1);
            path.pop_back();

        }
        }
    
    bool isPalindrome(string& s,int start,int end)
{
    for(int i=start,j=end;i<j;i++,j--)
    {
            if(s[i]!=s[j])
                return false;

    }
    return true;
}               
};

你可能感兴趣的:(leetcode刷题记录,c++)