leetcode_动态规划

2023-05-16

leetcode_动态规划

  • 基础题目
    • 509.斐波那契数
    • 70.爬楼梯
    • 62.不同路径
    • 63.不同路径II
    • 343.整数拆分
      • 96. 不同的二叉搜索树
  • 01背包
    • 分割等和子集
    • 1049.最后一块石头的重量II
    • 494.目标和
    • 474.一和零
  • 完全背包
    • 518.零钱兑换II
    • 组合总和IV
    • 70.爬楼梯(进阶版)
    • 零钱兑换
    • 279. 完全平方数
    • 139. 单词拆分
      • 回溯法
    • 动规法
    • 多重背包
      • 法一
    • 背包问题总结
  • 打家劫舍
    • 198.打家劫舍
    • 213.打家劫舍II
    • 打家劫舍III
      • 动规
      • 回溯
  • 买卖股票的最佳时机
    • 121.买卖股票的最佳时机
    • 122买卖股票的最佳时机II
    • 123.买卖股票的最佳时机III
    • 188.买卖股票的最佳时机IV
    • 309.最佳买卖股票时机含冷冻期
    • 714.买卖股票的最佳时机含手续费
  • 子序列问腿
    • 300.最长递增子序列
    • 674.最长连续递增序列
    • 718.最长重复子数组
    • 1143.最长公共子序列
    • 1035.不相交的线
    • 53.最大子序和
    • 392.判断子序列
    • 不同的子序列
    • 583.两个字符串的删除操作
    • 72.编辑距离
    • 编辑距离总结
    • 647.回文子串
      • 动态规划法
      • 双指针法
    • 516.最长回文子序列

在这里插入图片描述

基础题目

509.斐波那契数

class Solution {
public:
    int fib(int n) {
       if(n==0) return 0;
       vector<int> dp(n+1,0);
       dp[0] = 0;
       dp[1] = 1;
       for(int i =2;i<n+1;i++){
           dp[i] = dp[i-1]+dp[i-2];
       }
       return dp[n];
    }
};

70.爬楼梯

class Solution {
public:
    int climbStairs(int n) {
        if(n==1) return 1;
        vector<int> dp(n+1,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];
    }
};

62.不同路径

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int> > dp(m,vector<int>(n,1));
        for(int i =1;i<m;i++){
            for(int j =1;j<n;j++){
                dp[i][j] = dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
};

63.不同路径II

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();
        vector<vector<int> > dp(m,vector<int>(n,0));
        for(int i =0;i<n;i++){
            if(obstacleGrid[0][i]!=1)
                dp[0][i]=1;
            else break;
        }
        for(int i=0;i<m;i++){
            if(obstacleGrid[i][0]!=1)
                dp[i][0]=1;
            else break;
        }
        for(int i = 1;i<m;i++){
            for(int j =1;j<n;j++){
                dp[i][j] = dp[i-1][j]+dp[i][j-1];
                if(obstacleGrid[i][j]==1) dp[i][j]=0;
            }
        }
        return dp[m-1][n-1];
    }
};

343.整数拆分

整数拆分过程中我的递归数组写成了max(dp[i], max(j * (i - j), dp[j] * dp[i - j])),但正确的应该是dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j))
因为j * dp[i - j]是拆分成两个以及两个以上的个数相乘,而dp[i - j] * dp[j] 是默认将一个数强制拆成4份以及4份以上了,少算了。

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n + 1, 0);
        dp[2] = 1;
        for (int i = 3; i <= n; i++) {
            for (int j = 2; j <= i/2+1; j++) {
                dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]));
            }
        }
        return dp[n];
    }
};

96. 不同的二叉搜索树

class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n+1,0);
        dp[1] = 1;
        dp[0] = 1;
        for(int i =2;i<=n;i++){
            for(int j =0;j<i;j++){
                dp[i] += dp[j]*dp[i-j-1];
            }
        }
        return dp[n];
    }
};

01背包

  1. 分割等和子集:是否可以装满一个背包,物品的质量和价值相等,递推公式为dp[j] = max(dp[j], dp[j-nums[i]]+nums[i]);
  2. 最后一块石头的重量II:对于一个背包来说,最大可以装多重的重量,物品的价值和质量依然相等,递推公式为dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);
    3.目标和:装满一个背包,一共有多少种装物品的方式,物品的价值和质量相等,递推公式为dp[j]+=dp[j-nums[i]];
    4.一和零:装满一个背包,最多可以装多少个物品,物品的价值为1,递推公式为dp[i][j] = max(dp[i][j],dp[i-zeronum][j-onenum]+1),此问题只是质量的维度为2,而不是多重背包。

分割等和子集

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum=0;
        for(int i =0;i<nums.size();i++){
            sum+=nums[i];
        }
        if(sum%2==1) return false;
        int bageweight = sum/2;
        vector<int> dp(bageweight+1,0);
        for(int i =0;i<nums.size();i++){
            for(int j = bageweight;j>=nums[i];j--){
               dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        if(dp[bageweight]==bageweight) return true;
        return false;
    }
};

1049.最后一块石头的重量II

这道题用动规的思路很难想。首先将这道题的题意分析后变成尽量让石头分成重量相同的两堆,然后动规的target就是其中一堆,类比上一题分割等和子集。我的dp[target]代表在重量为target的背包里最多能装多重的石头。而且最终分成的两堆,一堆dp[target],一堆sum-dp[target]一定可以通过若干个回合抵消到只有sum-dp[bagweight]-dp[bagweight]。

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int sum=0;
        for(int i =0;i<stones.size();i++) sum+=stones[i];
        int  bagweight= sum/2;
        vector<int> dp(bagweight+1,0);
        for(int i =0;i<stones.size();i++){
            for(int j =bagweight;j>=stones[i];j--){
                dp[j] = max(dp[j],dp[j-stones[i]]+stones[i]);
            }
        }
        return sum-dp[bagweight]-dp[bagweight];
    }
};

494.目标和

只能说目标和这道题,我在做的时候还是用dp[j]=max(dp[j],dp[j-nums[i]]+nums[i])去了,然后用一个数ans统计次数,dp数组的意义没设对,应该直接设dp[j]:代表凑出j有几种方法。然后递推公式是dp[j] += dp[j-nums[i]] ,也就是说
在这里插入图片描述

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum=0;
        for(int i =0;i<nums.size();i++){
            if(nums[i]<0){
                nums[i]=-nums[i];
            }
            sum+=nums[i];
        }
        if(target>sum||target<-sum||(target+sum)%2!=0) return 0;
        int bagweight = (sum+target)/2;
        vector<int> dp(bagweight+1,0);
        dp[0]=1;
        for(int i =0;i<nums.size();i++){
            for(int j=bagweight;j>=nums[i];j--){
                dp[j] +=dp[j-nums[i]];
            }
        }
        return dp[bagweight];
    }
};

474.一和零

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int> > dp(m+1,vector<int>(n+1,0));
        for(string str:strs)
        {
            int zeronum=0,onenum=0;
            for(char c:str)
            {
                if (c =='0') zeronum++;
                else onenum++;
            }
                for(int i=m;i>=zeronum;i--)
                {
                    for(int j=n;j>=onenum;j--)
                    {
                        dp[i][j] = max(dp[i][j],dp[i-zeronum][j-onenum]+1);
                    }
                }
        }
        return dp[m][n];
    }
};

完全背包

  • 如果求组合数就是外层for循环遍历物品,内层for循环遍历背包
  • 如果求排列数就是外层for循环遍历背包,内层for循环遍历物品
  • 如果将爬楼梯改为一次1-m阶,那么这个问题就是一个完全背包问题了,而且求的是排列数,先遍历背包
  • 求装满背包需要的最少物品数,一是注意dp数组的初始化vector dp(bagweight,INT_MAX)。二是注意dp[0]=0这个条件的初始化,三是注意dp的递推公式dp[j] =min(dp[j],dp[j-nums[i]]+1);

每件物品有无限个,其他条件一样。
01背包和完全背包体现在遍历顺序上,我们知道01背包内嵌的循环是从大到小遍历,为了保证每个物品仅仅被添加一次。但是完全背包的物品可以被多次添加,所以,要从小到大遍历。

for(int i=0;i<weight.size();i++){
	for(int j =weight[i];j<=bagweight;j++){
		dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);
	}
}

518.零钱兑换II

本质上是装满一个背包,一共有多少种装物品的方式,把目标和那道题改成完全背包的遍历方式即可。注意!!!初始化dp[0]=1

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount+1,0);
        dp[0]=1;
        for(int i =0;i<coins.size();i++){
            for(int j =coins[i];j<=amount;j++){
                dp[j]+=dp[j-coins[i]];
            }
        }
        return dp[amount];
    }
};

组合总和IV

这道题求装满物品方式的个数的排列,排列和组合的区别是遍历方式,排列先便利背包,组合先遍历物品。

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target+1,0);
        dp[0]=1;
        for(int j =0;j<=target;j++){
            for(int i =0;i<nums.size();i++){
                if(j>=nums[i]&& dp[j] < INT_MAX - dp[j - nums[i]])dp[j] += dp[j-nums[i]];
            }
        }
        return dp[target];
    }
};

70.爬楼梯(进阶版)

题目改为一步一个台阶,两个台阶,三个台阶,…,直到m个台阶,问有多少种不同的方法可以爬到楼顶呢?
可以看成总的楼顶是背包,m阶是物品,这是一个完全背包问题,因为物品的数量为无限,且这是一个排列问题,因为(1,2,1)和(1,1,2)是两种走法。

求装满背包有几种方法,递推公式一般都是dp[j]+=dp[j-nums[i]],本题目中进一步改写为dp[j]+=dp[j-i];

class Solution {
public:
    int climbStairs(int n) {
       vector<int> dp(n+10);
       dp[0] = 1;
       int bagweighht;
       for(int j=1;i<=bagweight;i++){
       		for(int i =1;i<=m;i++){
       			dp[j]+=dp[j-i];
       		}
       }
       return dp[bagweight];
    }
};

零钱兑换

完全背包,求装满背包的最少可以装多少物品,迭代公式要用min。

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount+1,10010);
        dp[0] = 0;
        for(int i = 0;i<coins.size();i++){ //先遍历物品
            for(int j =coins[i];j<=amount;j++){ //再遍历背包
                dp[j] = min(dp[j],dp[j-coins[i]]+1);
            }
        }
        if(dp[amount]==10010) return -1;
        else return dp[amount];
    }
};

279. 完全平方数

class Solution {
public:
    int numSquares(int n) {
       vector<int> dp(n+1,INT_MAX);
       dp[0] = 0;
       for(int i =1;i*i<=n;i++){
           for(int j =i*i;j<=n;j++){
               dp[j] = min(dp[j],dp[j-i*i]+1);
           }
       }
       return dp[n]; 
    }
};

139. 单词拆分

难理解的动规法,自己做加分析两种方法花了1h。

回溯法

//回溯法
class Solution {
public:
    bool backtracking(string s, unordered_set<string>& wordset,vector<bool>& memory,int startindex){
        if(startindex>=s.size()){
            return true;   //一般不会到这步,到这步说明str中的拆分字符串都在wordDict中找到了,自然要返回true
        }
        if(!memory[startindex]) return false;   //使用memory剪枝。
        for(int i =startindex;i<s.size();i++){
            string str = s.substr(startindex,i-startindex+1);
            if(wordset.count(str)!=0&& backtracking(s,wordset,memory,i+1)){ //如果该字符串str可被拆,并且str后面的字符串也可以被拆,则返回true
                return true;
            }
        }
        memory[startindex] = false;
        return false;
    }
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> wordset(wordDict.begin(), wordDict.end());
        //回溯+记忆化递归,相当于剪枝
        vector<bool> memory(s.size(),true);  //从startindex开始的字符串是否可被拆分,如果不可设为false,初始化全部为true
        return backtracking(s,wordset,memory,0);
    }
};

动规法

//回溯法
class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        //dp[i]数组的含义:字符串长度为i时,是否可以被拆分成wordDict中的子串
        //物品是字符串,背包大小就是s的大小
        //递推公式:if(dp[i-j]&&wordset.count(str)!=0) dp[i]=false;
        vector<bool> dp(s.size()+1,false);
        dp[0] =true;
        unordered_set<string> wordset(wordDict.begin(),wordDict.end());
        //求排列,因为字符串是有顺序的,所有要遍历所有排列数
        for(int j = 1;j<=s.size();j++){
            for(int i =0;i<=j;i++){
                string str = s.substr(i,j-i); //j是长度,所以不用减一
                if(wordset.count(str)!=0&&dp[i]) dp[j]=true; 
                if(dp[j]==true) break;
            }
        }
        return dp[s.size()]; 
    }
};

多重背包

多重背包化为0-1背包

法一

直接将数量进行拆解

在这里插入图片描述

void test_multi_pack() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    vector<int> nums = {2, 3, 2};
    int bagWeight = 10;
    for (int i = 0; i < nums.size(); i++) {
        while (nums[i] > 1) { // nums[i]保留到1,把其他物品都展开
            weight.push_back(weight[i]);
            value.push_back(value[i]);
            nums[i]--;
        }
    }

    vector<int> dp(bagWeight + 1, 0);
    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
        for (int j = 0; j <= bagWeight; j++) {
            cout << dp[j] << " ";
        }
        cout << endl;
    }
    cout << dp[bagWeight] << endl;

}
int main() {
    test_multi_pack();
}

背包问题总结

在这里插入图片描述

打家劫舍

198.打家劫舍

class Solution {
public:
    int rob(vector<int>& nums) {
      //dp数组的含义:当到达第下标为第i家的时候最多可以偷窃的金钱数
      vector<int> dp(nums.size(),0);
      if(nums.size()==1) return nums[0];
      dp[0] = nums[0];
      dp[1] = max(nums[0],nums[1]);
      for(int i =2;i<nums.size();i++){
          dp[i] = max(dp[i-1],dp[i-2]+nums[i]);
      }
      return dp[nums.size()-1];
    }
};

213.打家劫舍II

//成环需要考虑两种情况,不偷首,不偷尾
class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size()==1) return nums[0];
        int res1 = result(nums,1,nums.size()-1); //不偷首
        int res2 = result(nums,0,nums.size()-2); //不偷尾
        return max(res1,res2);
        
    }
    int result(vector<int>& nums,int start,int end)
    {
       vector<int> dp(end-start+1,0);
       if(dp.size()==1) return nums[start];
       dp[0] = nums[start];
       dp[1] = max(nums[start+1],nums[start]);
       for(int i =2;i<dp.size();i++){
           dp[i] = max(dp[i-1],dp[i-2]+nums[start+i]);
       }
       return dp[dp.size()-1];
    }
};

打家劫舍III

动规

class Solution {
public:
    int rob(TreeNode* root) {
        vector<int> res=robTree(root);
        return max(res[0],res[1]);
    }
    vector<int> robTree(TreeNode* cur)
    {
       if(cur==nullptr) return vector<int> {0,0};
       vector<int> left = robTree(cur->left);
       vector<int> right = robTree(cur->right);

       int val1 = cur->val+left[0]+right[0];
       int val2 = max(left[0],left[1])+max(right[0],right[1]);
       return {val2,val1};
    }
};

回溯

这道题回溯+剪枝竟然比动规快,主要还是在于记忆化数组memory的运用。

class Solution {
public:
    unordered_map<TreeNode*,int> memory;
    int rob(TreeNode* cur) {
        if(cur==nullptr) return 0;
        if(cur->left==nullptr&&cur->right==nullptr) return cur->val;
        if(memory[cur]) return memory[cur];
        // 偷该节点
        int val1 = cur->val;
        int val2 = 0;
        if(cur->left) 
        {
            val1+=rob(cur->left->left)+rob(cur->left->right);
            val2+=rob(cur->left);
        }
        if(cur->right) {
            val1+=rob(cur->right->left)+rob(cur->right->right);
            val2+=rob(cur->right);
        }
        memory[cur] = max(val1,val2);
        return max(val1,val2);
    }
};

买卖股票的最佳时机

代码随想录总结

  1. 买卖股票的最佳时机:只可以买卖一次,递推公式为:
    dp[i][0] = max(dp[i-1][0],-prices[i]);
    dp[i][1] = max(dp[i-1][1],prices[i]+dp[i-1][0]);
  1. 买卖股票的最佳时机II:可以买卖多次,递推公式为:
    dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); // 注意这里是和121. 买卖股票的最佳时机唯一不同的地方。
    dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
  1. 买卖股票的最佳时机III:只可以买卖两次,递推公式为:
    dp[i][0] = dp[i-1][0];
    dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]);
    dp[i][2] = max(dp[i-1][2],dp[i-1][1]+prices[i]);
    dp[i][3] = max(dp[i-1][3],dp[i-1][2]-prices[i]);
    dp[i][4] = max(dp[i-1][4],dp[i-1][3]+prices[i]);
  1. 买卖股票的最佳时机IV:可以买卖k次,递推公式为:
    for(int j =1;j<=2*k;j+=2){
    dp[i][j] = max(dp[i-1][j],dp[i-1][j-1]-prices[i]);
    dp[i][j+1] = max(dp[i-1][j+1],dp[i-1][j]+prices[i]);
    }
  1. 买卖股票的最佳时机含冷冻期:
    dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]));
    dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
    dp[i][2] = dp[i - 1][0] + prices[i];
    dp[i][3] = dp[i - 1][2];
  1. 买卖故交的最佳时机含手续费:无限次交易:
    dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
    dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);

121.买卖股票的最佳时机

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        //dp[i][0]:第i天持有股票获得的最大现金
        //dp[i][1]:第i天不再持有股票获得的最大现金
        vector<vector<int> > dp(prices.size(),vector<int>(2,0));
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for(int i =1;i<prices.size();i++){
            dp[i][0] = max(dp[i-1][0],-prices[i]);
            dp[i][1] = max(dp[i-1][1],prices[i]+dp[i-1][0]);
        }
        return dp[prices.size()-1][1];
    }
};

122买卖股票的最佳时机II

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        vector<int> dp(prices.size(),0);
        for(int i =1;i<prices.size();i++){
                dp[i] = dp[i-1]+max(prices[i]-prices[i-1],0);
        }
        return dp[prices.size()-1];
    }
};

123.买卖股票的最佳时机III

class Solution {
public:
    int maxProfit(vector<int>& prices) {
    //dp[i][0]:没有操作
    //dp[i][1]:第一次买入股票拥有的最大现金数
    //dp[i][2]:第一次卖出股票拥有的最大现金数
    //dp[i][3]:第二次买入股票拥有的最大现金数
    //dp[i][4]:第二次卖出股票拥有的最大现金数
        vector<vector<int> > dp(prices.size(),vector<int>(5,0));
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        dp[0][2] = 0;
        dp[0][3] = -prices[0];
        dp[0][4] = 0;
        for(int i =1;i<prices.size();i++){
            dp[i][0] = dp[i-1][0];
            dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]);
            dp[i][2] = max(dp[i-1][2],dp[i-1][1]+prices[i]);
            dp[i][3] = max(dp[i-1][3],dp[i-1][2]-prices[i]);
            dp[i][4] = max(dp[i-1][4],dp[i-1][3]+prices[i]);
        }
        return dp[prices.size()-1][4];
    }
};

188.买卖股票的最佳时机IV

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        vector<vector<int> > dp(prices.size(),vector<int>(2*k+1,0));
        for(int j=1;j<=2*k;j+=2){
            dp[0][j] = -prices[0];
            dp[0][j+1] = 0;
        }
        for(int i =1;i<prices.size();i++){
            for(int j =1;j<=2*k;j+=2){
                dp[i][j] = max(dp[i-1][j],dp[i-1][j-1]-prices[i]);
                dp[i][j+1] = max(dp[i-1][j+1],dp[i-1][j]+prices[i]);
            }
        }
        return dp[prices.size()-1][2*k];
    }
};

309.最佳买卖股票时机含冷冻期

在这里插入图片描述

class Solution {
public:
    int maxProfit(vector<int>& prices) {
    // 状态一:持有股票状态(今天买入股票,或者之前就买入了股票然后没有操作,一直持有)的金钱数
    // 状态二:保持卖出股票的状态(两天前卖出了股票,度过一天冷冻期,或者前一天就是卖出股票状态)的金钱数
    // 状态三:今天卖出股票的金钱数
    // 状态四:今天是冷冻期状态,但冷冻期只有一天的金钱数
        if(prices.size()==0) return 0;
        vector<vector<int> > dp(prices.size(),vector<int>(4,0));
        dp[0][0] = -prices[0];
        for(int i =1;i<prices.size();i++){
            dp[i][0] = max(dp[i-1][0],max(dp[i-1][3]-prices[i],dp[i-1][1]-prices[i]));
            dp[i][1] = max(dp[i-1][3],dp[i-1][1]);
            dp[i][2] = dp[i-1][0]+prices[i];
            dp[i][3] = dp[i-1][2];
        }
        return max(dp[prices.size() - 1][3], max(dp[prices.size() - 1][1], dp[prices.size() - 1][2]));
    }
};

714.买卖股票的最佳时机含手续费

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        vector<vector<int>> dp(n, vector<int>(2, 0));
        dp[0][0] -= prices[0]; // 持股票
        for (int i = 1; i < n; i++) {
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
        }
        return max(dp[n - 1][0], dp[n - 1][1]);
    }
};

子序列问腿

300.最长递增子序列

dp[i]:表示i之前包括i的以nums[i]结尾的最长递增子序列的长度
状态转移方程:
位置i的最长升序子序列等于j从0到i-1的各个位置的最长的升序子序列+1的最大值

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size()<=1) return nums.size();
        vector<int> dp(nums.size(),1);
        int result = 0;
        for(int i =1;i<nums.size();i++){
            for(int j =0;j<i;j++){
                if(nums[i]>nums[j]) dp[i] = max(dp[i],dp[j]+1);
            }
            result = dp[i]>result?dp[i]:result;
        }
        return result;
    }
};

674.最长连续递增序列

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

718.最长重复子数组

  • dp[i][j]:以下标i-1为结尾的A和以下标j-1为结尾的B,最长重复子数组长度为dp[i][j],
class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int> > dp(nums1.size()+1,vector<int>(nums2.size()+1,0));
        int res=0;
        for(int i =1;i<=nums1.size();i++){
            for(int j =1;j<=nums2.size();j++)
            {
                if(nums1[i-1]==nums2[j-1]){
                    dp[i][j] = dp[i-1][j-1]+1;
                }
                if(dp[i][j]>res) res = dp[i][j];
            }
        }
        return res;
    }
};

1143.最长公共子序列

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

1035.不相交的线

这道题本质上还在求最长公共子序列,记住最长公共子序列的迭代公式就可以a掉这道题。

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

53.最大子序和

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        vector<int> dp(nums.size(),0);
        dp[0] = nums[0];
        int res = dp[0];
        for(int i =1;i<nums.size();i++)
        {
            dp[i] = max(dp[i-1]+nums[i],nums[i]);
            res = dp[i]>res?dp[i]:res;
        }
        return res;
    }
};

392.判断子序列

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

不同的子序列

这道题用dp做很难想到

//dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]
class Solution {
public:
    int numDistinct(string s, string t) {
        vector<vector<uint64_t> > dp(s.size()+1,vector<uint64_t>(t.size()+1));
        //以下标0~i-1结尾的s串中,出现0字符串的个数
        //只有dp[0][0] = 1,其他都是复制的dp[0][0]
        for(int i = 0;i<s.size();i++) dp[i][0] = 1;
        for(int j = 1;j<t.size();j++) dp[0][j] = 0;
        for (int i = 1; i <= s.size(); i++) {
            for (int j = 1; j <= t.size(); j++) {
                if (s[i - 1] == t[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[s.size()][t.size()];
    }
};

根据dp数组的含义,我主要想了很久的如果s[i - 1] == t[j - 1],那么dp数组如何更新。如果使用s[i-1]去匹配,那么以i-1为结尾的s子序列中出现以j-1为结尾的t的个数就是以i-2为结尾的s子序列中出现以j-2为结尾的t的个数,即dp[i-1][j-1]。如果不使用s[i-1]去匹配,那么dp[i][j] = dp[i-1][j] ,所以此时dp[i][j]是两种情况的和。dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]

举个例子
s = “rabbbit”, t = “rabbit”
当s遍历到第2个b时,可以使用第二b去配,此时就是ra[]b,不使用第二b去配时,此时就是rab[]

583.两个字符串的删除操作

class Solution {
public:
    //dp[i][j]:以i-1为结尾的字符串word1和以j-1为结尾的字符串word2,想要达到相等,所需要删除元素的最少次数
    int minDistance(string word1, string word2) {
        vector<vector<int> > dp(word1.size()+1,vector<int>(word2.size()+1,0));
        //以i-1为结尾的字符串word1和空字符串的word2,想要达到相等,所需要删除元素的最少次数
        //以j-1为结尾的字符串word2和空字符串的word1,想要达到相等,所需要删除元素的最少次数
        //注意:j=1,才代表下标为0
        for(int i =0;i<=word1.size();i++) dp[i][0] = i;
        for(int j =0;j<=word2.size();j++) dp[0][j] = j;
        for(int i =1;i<=word1.size();i++){
            for(int j =1;j<=word2.size();j++){
                //如果word1[i-1]==word2[j-1] 不删
                if(word1[i-1]==word2[j-1]) dp[i][j] = dp[i-1][j-1];
                //否则删除其中一个
                else dp[i][j] = min(dp[i][j-1]+1,dp[i-1][j]+1);
            }
        }
        return dp[word1.size()][word2.size()];
    }
};

72.编辑距离

class Solution {
public:
    //本题和583两个字符的删除操作非常类似
    //dp[i][j]的含义为以下标i-1为结尾的字符串word1和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]
    //删除和添加需要的操作数次数是一样的:
    //word1:ad word2:a ,删除word1的d和增加word2的d都需要一次。所以延续583题的递推公式
    //word1 word2替换操作,此时dp[i][j] = dp[i-1][j-1]+1
    //因为dp[i][j] = dp[i-1][j-1]word1[i-1]和 word2[j-1]相等时的递推公式,只需再增加1,就变成了word1[i-1]==word2[j-1]这种情况
    int minDistance(string word1, string word2) {
        vector<vector<int> > dp(word1.size()+1,vector<int>(word2.size()+1,0));
        for(int i = 0;i<=word1.size();i++) dp[i][0] = i;
        for(int j = 0;j<=word2.size();j++) dp[0][j] = j;
        for(int i = 1;i<=word1.size();i++){
            for(int j =1;j<=word2.size();j++){
                if(word1[i-1]==word2[j-1]) dp[i][j] = dp[i-1][j-1];
                else dp[i][j] = min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
            }
        }
        return dp[word1.size()][word2.size()];
    }
};

编辑距离总结

关于编辑距离的问题,我们从判断子序列开始一共做了4个题目。

判断子序列:给定字符串s和t是否为t的子序列实际上编辑距离不用考虑替换和添加操作的一道题目。

dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。

if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = dp[i][j - 1];

相同,则长度+1,不同则长度沿袭dp[i][j-1]

不同的子序列:给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。

if (s[i - 1] == t[j - 1]) {
   dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
   //一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。
   //即不需要考虑当前s子串和t子串的最后一位字母,所以只需要 dp[i-1][j-1]。
   //一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。
} else {
    dp[i][j] = dp[i - 1][j];
    //当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配(就是模拟在s中删除这个元素),即:dp[i - 1][j]
}

两个字符串的删除操作:给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。

dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
当word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];

当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:

情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1

情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1

情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2

那最后当然是取最小值,所以当word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});

因为 dp[i][j - 1] + 1 = dp[i - 1][j - 1] + 2,所以递推公式可简化为:dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
在这里插入图片描述

编辑距离:给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符

dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。

 //本题和583两个字符的删除操作非常类似
    //dp[i][j]的含义为以下标i-1为结尾的字符串word1和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]
    //删除和添加需要的操作数次数是一样的:
    //word1:ad word2:a ,删除word1的d和增加word2的d都需要一次。所以延续583题的递推公式
    //word1 word2替换操作,此时dp[i][j] = dp[i-1][j-1]+1
    //因为dp[i][j] = dp[i-1][j-1]word1[i-1]和 word2[j-1]相等时的递推公式,只需再增加1,就变成了word1[i-1]==word2[j-1]这种情况
 if(word1[i-1]==word2[j-1]) dp[i][j] = dp[i-1][j-1];
 else dp[i][j] = min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;

647.回文子串

动态规划法

  1. 确定dp数组以及下标的含义:
    布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
  2. 确定递推公式:
    当s[i]与s[j]不相等,dp[i][j]一定是false。
    当s[i]与s[j]相等时,如果j-i<=1,则依然是回文子串。如果j-i>1,则是否是回文子串依赖于dp[i+1][j-1]
    在这里插入图片描述
class Solution {
public:
    int countSubstrings(string s) {
        vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
        int res= 0;
        for(int i =s.size()-1;i>=0;i--){
            for(int j =i;j<s.size();j++){
                if(s[i]!=s[j]) dp[i][j]=false;
                else{
                    if(j-i<=1){
                        dp[i][j] = true;
                        res++;
                    } 
                    else if(dp[i+1][j-1]){
                        dp[i][j] = true;
                        res++;
                    } 
                }
            }
        }
        return res;
    }
};

双指针法

class Solution {
public:
    int countSubstrings(string s) {
        int res=0;
        for(int i =0;i<s.size();i++){
            res+=extend(s,i,i,s.size());
            res+=extend(s,i,i+1,s.size());
        }
        return res;
    }
    int extend(string& s,int i, int j, int n){
        int res = 0;
        while(i>=0&&j<n&&s[i]==s[j]){
            i--;
            j++;
            res++;
        }
        return res;
    }
};

516.最长回文子序列

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        vector<vector<int> > dp(s.size(),vector<int>(s.size(),0));
        for(int i =0;i<s.size();i++) dp[i][i]=1;
        for(int i = s.size()-2;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+1][j],dp[i][j-1]);
            }
        }
        return dp[0][s.size()-1];
    }
};
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

leetcode_动态规划 的相关文章

  • 关于jetson TX2 USB lanes 映射表ODM值更新的问题

    jetson TX2不支持不通过重刷系统进行更新ODM值支持在刷机命令行指定ODM的值 xff0c 方式 xff1a o
  • Jetson TX1/TX2搭载RTSO-9003载板的系统烧录及Jetpack安装软件包

    入手jetson TX1 TX2之后 xff0c 首先面临的是要刷机烧系统 xff0c 因为NVIDIA的TX1或TX2是不带系统的 同时入手RTSO 9003超小型载板 xff0c 跟模块一个尺寸 满足一般的使用需求 根据NVIDIA的l
  • 使用Jetpack给jetson tx1/tx2安装cuDNN、TensorRT、CUDA等软件环境

    背景 该博客适用于jetson设备已经装好了系统 xff0c 对于系统怎么烧录可以参考系统刷机 注意 使用Jetpack给jetson tx1 tx2安装软件之前需先确定jetson设备系统l4t版本 xff0c 因为NVDIA jetpa
  • C语言之strtok函数

    FROM MSDN amp amp 百科 原型 xff1a char strtok char s const char delim include lt string h gt 分解字符串为一组字符串 s为要分解的字符串 xff0c del
  • 谷粒学院-项目功能模块、技术点、整合Mybatis-Plus、主键生成策略

    Mybaitis Plus 简介 MyBatis Plus 初始化工程 使用 Spring Initializr 快速初始化一个 Spring Boot 工程 Group xff1a com nanjing Artifact xff1a m
  • sql中SUM函数的值保留两位小数

    SUM convert decimal 19 2 Num1 Num2 AS quantity
  • IdentityServer4官方文档代码配置unauthorized_client Invalid grant type for client错误

    今天按照IdentityServer4官方文档写了一下代码测试下来报错 xff0c 官方文档配置ConfigureService代码如下图 官方配置Configure代码如下图 运行报错效果如下图 完全按照官方文档跑的 xff0c 然后找了
  • 【MATLAB】(一)基本使用入门

    本文目录 前言声明一 MATLAB简介 xff08 基本来自课程内容 xff09 二 入门操作2 1 命令窗口 xff08 Command Window xff09 2 1 1 基本介绍2 1 2 基础指令2 1 3 常用操作 2 2 编辑
  • 【软件相关】Multisim完整教程

    文章目录 前言教程书籍安装基本使用快捷键总结高级操作1 示波器操作2 瞬态仿真3 MCU仿真 问题与解决1 添加场效应管不符合电气规律 xff1f 前言 电路仿真软件中 xff0c Multisim可能不是功能最强大的 xff0c 但一定是
  • 【单片机】C语言总结

    文章目录 文章内容1 位运算1 1 初级应用1 2 进阶应用1 2 1 给寄存器某一位置11 2 2 给寄存器某一位清01 2 3 翻转寄存器某一位1 2 4 数据的字节分解 2 宏定义和预编译2 1 理论指导2 2 看到一个宏定义在数据切
  • 【嵌入式工具】Keil下载,安装,配置教程大全

    文章目录 前言一 Keil下载及安装二 Keil兼容C51和ARM三 STM32支持包下载安装1 官网下载2 安装 四 常用配置1 代码补全和代码联想2 主题设置3 快捷键设置4 快速模板5 快速格式化代码6 转换文件编码格式 2022 3
  • 【嵌入式模块】蓝牙模块使用总结

    目录 前言参考链接常用的蓝牙模块有哪几种 xff1f 如何设置蓝牙模块 xff1f AT指令集BT 04HC 06HC 05 蓝牙主从配对工作手机与电脑端调试方法 前言 作为最为常用的无线通信模块 xff0c 蓝牙可以说是一些小型项目 xf
  • 【嵌入式模块】DS1302 时钟定时芯片

    文章目录 参考链接概述引脚与内部结构引脚定义常用电路内部寄存器及RAM分布 工作时序例程 xff08 51单片机 xff09 参考链接 CSDN 1 CSDN 2 博客园 概述 DS1302时钟芯片是DALLAS 公司推出的涓流充电时钟芯片
  • 【嵌入式模块】直流电机驱动L298N,TB6612详解

    文章目录 参考链接概述L298NTB6612FNG 参考链接 单片机 控制 直流电机 基于L9110S L298N TB6612FNG驱动 简书 概述 从上面那篇教程我们可以看出 xff0c 直流电机控制时 xff0c 只需要给它输入一个P
  • vue .npmrc 文件的作用

    有些项目根目录下有个 npmrc的文件 xff0c 点开看只有一句话 xff1a span class token assign left variable registry span span class token operator 6
  • 【嵌入式模块】ESP8266完整教程

    前言 无线通信中除了最为常用的蓝牙之外 xff0c 剩下的就是WiFi了 xff0c 但是相比于蓝牙模块一般只用来进行透传 xff0c WiFi模块的可自定义程度要更强 xff0c 而这也导致了WiFi模块的入门相对难了一点 参考资料 WI
  • 【MATLAB】(二)基本使用拾遗

    本文目录 0 前期教程1 前言2 输入输出2 1 input2 2 load2 3 importdata2 4 disp2 5 fopen amp fclose2 6 fscanf amp fprintf2 7 textread amp t
  • 【Linux】Ubuntu使用入门

    前言 本文主要记录一些Ubuntu中常用的基本操作 xff0c 记录自己的实践经历 xff0c 不断更新 xff01 xff01 xff01 0 基本文件交互 在Ubuntu系统中 xff0c 右键是没有创建文件的选项的 xff0c 只能创
  • 【嵌入式模块】MPU6050

    文章目录 0 前言1 MPU6050概述1 1 基本概述1 2 引脚和常用原理图 2 代码3 姿态解算3 1 欧拉角 amp 旋转矩阵3 2 DMP 3 校正 0 前言 作为惯性传感器中入门级别的器件 xff0c MPU6050凭借它出色的
  • 【PyQt】PyQt5进阶——串口上位机及实时数据显示

    文章目录 0 前期教程1 前言2 串口部分 QtSerialPort3 绘图部分3 1 QCustomPlot3 2 QtChart3 3 QWT3 4 Qt Designer中如何使用 参考链接 0 前期教程 Python PyQt5入门

随机推荐

  • 【软件相关】Proteus仿真STM32记录

    文章目录 0 前期教程1 前言2 先说说建议的流程3 需要注意的事项3 1 供电网配置不要忘了3 2 ADC模块的使用3 3 元器件查询手册 4 一些小技巧4 1 快速添加标号4 2 出现诡异问题的一种解决思路 0 前期教程 软件相关 Pr
  • 【嵌入式】Modbus实践

    前言 最近接了一个项目 xff0c 需要使用Modbus协议 xff0c 虽然之前有所耳闻 xff0c 但一直没有实操过 xff0c 但实践之后发现其实还是很简单的 xff0c 我认为它本质上就是对串口传输进行 二次封装 建议的入门顺序 大
  • 正则 ^ , \G , \A 区别

    正则 G A 区别 如图
  • c的string库常用函数记录

    1 strcat x xff0c y 将字符串y拼接在字符串x后面 2 strlen xff08 x xff09 返回字符串x的长度 3 strcopy xff08 x xff0c y xff09 将y复制给x xff08 类似于x 61
  • ROS发行版列表完整版

    官方原文 xff1a http wiki ros org Distributions Distro Release date EOL date ROS Melodic Morenia Recommended May 23rd 2018 Ma
  • React styled-components(三)—— 高级特性

    styled components 高级特性 样式继承嵌套设置主题 样式继承 新建 Demo js 文件 xff1a span class token keyword import span React span class token p
  • (九)Java算法:快速排序(详细图解)

    目录 一 前言1 1 概念1 2 算法过程 二 maven依赖三 流程解析3 1 全部数据分区3 2 左边数据分区3 3 右边数据分区 四 编码实现结语 一 前言 1 1 概念 快速排序 xff1a 用数组的第一个数作为基准数据 xff0c
  • 【Linux】树莓派控制光强传感器(C、python手把手教学)

    本文分为三个部分 xff1a 1 光强传感器说明 2 程序解读 3 前期准备 xff08 放在最后一部分 xff0c 供小白查阅借鉴 xff0c 包括本文需要用到的wiringPi库函数 xff09 一 光强传感器说明 1 TSL256x
  • Ubuntu安装VNC,配置多用户vnc连接Ubuntu,开机自启vnc命令

    Ubuntu安装VNC span class token function sudo span span class token function apt span update span class token function sudo
  • 解决登陆github慢的问题

    解决方法 首先本文解决的问题是Github网站可以访问 xff0c 但是由于网络代理商的原因 xff0c 造成访问速度很慢 Ping www github com 时 xff0c 速度只有200多ms 解决思路 xff1a 1 可以花钱购买
  • 什么是反卷积(快速理解)

    什么是反卷积 参考博客 我们知道输入图像通过卷积神经网络 xff08 CNN xff09 提取特征后 xff0c 输出的尺寸往往会变小 xff0c 而又是我们需要将图像恢复到原来的尺寸以便进行进一步的计算 xff0c 整个扩大图像尺寸 xf
  • 李雅普诺夫稳定(内部稳定)与BIBO稳定(外部稳定)的关系

  • 情绪识别论文阅读

    情绪识别论文阅读 情感脑机接口研究综述一种基于情感脑电信号时 频 空特征的3D密集连接网络 1 吕宝粮 张亚倩 郑伟龙 情感脑机接口研究综述 J 智能科学与技术学报 2021 3 01 36 48 情感脑机接口研究综述 情感脑机接口研究面临
  • 一文详细介绍情绪识别常用的数据集

    一文详细介绍情绪识别常用的数据集 SEED采集情况文件介绍 SEED IV采集情况文件介绍 CIAIC多模态情感识别数据采集情况文件介绍 DEAP采集情况文件情况 SEED V采集情况文件情况 本文详细介绍了脑机接口情绪识别常用的数据集 x
  • 父子进程虚拟地址空间情况

    父子进程虚拟地址空间情况 笔记来源于牛客网 Linux多进程开发 The child process and the parent process run in separate memory spaces At the time of f
  • Pytorch中nn.Module中的self.register_buffer解释

    self register buffer作用解释 今天遇到了这样一种用法 xff0c self register buffer name Tensor xff0c 该方法的作用在于定义一组参数 该组参数在模型训练时不会更新 xff08 即调
  • leetcode_回溯算法

    回溯算法刷题总结 回溯法理论基础回溯算法的模板组合问题77 组合优化版本 216 组合总和III17 电话号码的字母组合组合总和组合总和II 分割131 分割回文串93 复原IP地址 子集78 子集90 子集II491 递增子序列 xff0
  • React styled-components(二)—— props、attrs属性

    styled components props attrs属性 96 props 96 96 props 96 穿透添加 96 attrs 96 属性获取 96 state 96 中的样式 变量控制样式通过 96 props 96 控制样式
  • leetcode_贪心算法

    贪心算法相关题 简单题目455 分发饼干1005 K次取反后最大化的数组和860 柠檬水找零 序列问题376 摆动序列法一 xff1a 贪心法法二 xff1a 动态规划 单调递增的数字简化版本 有点难度53 最大子序和贪心算法动态规划 13
  • leetcode_动态规划

    leetcode 动态规划 基础题目509 斐波那契数70 爬楼梯62 不同路径63 不同路径II343 整数拆分96 不同的二叉搜索树 01背包分割等和子集1049 最后一块石头的重量II494 目标和474 一和零 完全背包518 零钱