【代码随想录】动态规划经典题

news2024/9/23 5:21:11

前言

更详细的在大佬的代码随想录 (programmercarl.com)

本系列仅是简洁版笔记,为了之后方便观看

 做题步骤

含义公式初始化顺序检查

  1. 确定dp数组以及下标的含义
  2. 递推公式
  3. dp数组如何初始化
  4. 遍历顺序
  5. 打印dp数组(看哪里有问题)

斐波那契数 

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

    }
};

爬楼梯 

70. 爬楼梯 - 力扣(LeetCode)

代码思路和上一题相差不大,主要是初始化和遍历时i的取值不同。 

class Solution {
public:
    int climbStairs(int n) {
        if (n <= 1) return n; 
        vector<int> dp(n + 1);
        dp[1] = 1;//初始化要根据实际情况进行改变
        dp[2] = 2;
        for (int i = 3; i <= n; i++) { 
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
};

 使用最小花费爬楼梯

746. 使用最小花费爬楼梯 - 力扣(LeetCode)

爬楼梯的消费版

dp表示的是到达本层已经使用的体力,cost[i] 是从本台阶向上爬需要支付的费用

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        vector<int> dp(cost.size() + 1);
        dp[0] = 0;//根据题意设计
        dp[1] = 0;
        for (int i = 2; i <= cost.size(); i++) {
            dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        }
        return dp[cost.size()];
    }
};

不同路径

62. 不同路径 - 力扣(LeetCode)

题意:只能向下向右到目标地点,求路径数

dp[i][j] 只和 dp[i - 1][j] 和dp[i][j - 1]有关,很容易造成的观点错误是dp[i - 1][j]+1和dp[i][j - 1]推导而来,但是要清楚的是本题求得是路径数,dp[i - 1][j] 只能向下走,dp[i][j - 1]只能向右走,所以路径数不变

class Solution {
public:
    int uniquePaths(int m, int n) {
       vector<vector<int>>dp(m,vector<int>(n,0));
       for (int i = 0; i < m; i++) dp[i][0] = 1;
       for (int j = 0; j < n; j++) dp[0][j] = 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];
    }
};

不同路径 II

和上一题的不同点:障碍物的出现

代码不同点:遍历顺序添加限制条件,不同路径初始化要改变

没有障碍物的时候才可以正常遍历 

if (obstacleGrid[i][j] == 0) { 
    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
vector<vector<int>> dp(m, vector<int>(n, 0));
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;

整数拆分

. - 力扣(LeetCode)

拆成若干数使得相乘最大

技巧:拆分成多个近似相等的数 

难思考点:遍历顺序

j * (i - j) :把整数拆分为两个数相乘,

j * dp[i - j]:拆分成两个以及两个以上的个数相乘

for (int i = 3; i <= n ; i++) {
    for (int j = 1; j < i - 1; j++) {
        dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
    }
}
class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n + 1);
        dp[2] = 1;//dp[0]和dp[1]都是0 因为不需要拆分
        for (int i = 3; i <= n ; i++) {
            for (int j = 1; j <= i / 2; j++) {
                dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));//求取最大值
           
        }
        return dp[n];//返回全部遍历后的最大值
    }
};

 不同的二叉搜索树

96. 不同的二叉搜索树 - 力扣(LeetCode)

通过举例子发现重叠子问题

代码很简单,主要是思路问题,知道二叉搜索树的概念,并分别对左右子树进行计算,相乘 

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

01背包

二维01背包

dp[i][j]表示 [0-i]的物品里任意取容量为j的背包的价值

  • 不放物品i:dp[i][j]=dp[i - 1][j]
  • 放物品i:dp[i][j]=dp[i - 1][j - weight[i]] + value[i] 
  • 注意:非零下标不管初始化什么值,都不影响最后结果,但是有零下表初始化需要在注意
  • dp[0][j],当 j < weight[0]的时候,放不进去,dp[0][j] = 0;当j >= weight[0]时,dp[0][j] =value[0]

  • vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
    for (int j = weight[0]; j <= bagweight; j++) {
            dp[0][j] = value[0];
        }
  • 二维数组实现的dp01背包for循环可以颠倒

  • for(int i = 1; i < weight.size(); i++) { // 物品
         for(int j = 0; j <= bagweight; j++) { // 背包
           if (j < weight[i]) dp[i][j] = dp[i - 1][j];
             else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
            }
        }

一维01背包 

dp[j]容量为j的背包的价值

  • 不放物品i:dp[j]=dp[j]
  • 放物品i:dp[j]=dp[j - weight[i]] + value[i] 
  • 注意:非零下标不管初始化什么值,都不影响最后结果,但是有零下标初始化为非负数的最小值0就可以
  • vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
    for (int j = weight[0]; j <= bagweight; j++) {
            dp[0][j] = value[0];
        }
  • 一维数组实现的dp01背包for循环不可以颠倒,背包必须倒序输出,这样才能符合每个背包只能使用一次

  •   vector<int> dp(N + 1, 0);
        for (int i = 0; i < 物品数量; ++i) {
            for (int j = N; j >= costs[i]; --j) {
                  dp[j] = max(dp[j], dp[j - costs[i]] + values[i]);
            }
        }

分割等和子集 

416. 分割等和子集 - 力扣(LeetCode)

把数组分割成两个元素和相等的子集,可以弄成01背包问题,每个数只能使用一次,观察是否能把num/2的背包容量给填满

注意:本题重量和价值是统一变量

dp[j] == j 时集合中的子集总和正好可以凑成总和j

class Solution {
public:
    bool canPartition(vector<int>& nums) {
         int sum=0;
         vector<int>dp(10001,0);
         for(int i=0;i<nums.size();i++){
            sum+=nums[i];
         }
         if(sum%2==1) return false;//说明凑不成两个一样的数
         int target=sum/2;
         for(int i=0;i<nums.size();i++){
            for(int j = target; j >= nums[i]; j--) {
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            } 
         }
        if (dp[target] == target) return true;
        return false;    
    }
};

最后一块石头的重量II

1049. 最后一块石头的重量 II - 力扣(LeetCode)

两两石头相撞,最终取得最小差值,可以分成两个数量之和相近的堆,来进行计算是上一题的演变,重量和价值是统一变量

target = sum / 2向下取整,所以sum - dp[target] >=dp[target],,所以最终结果就是用大的减去小的

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

目标和 

 494. 目标和 - 力扣(LeetCode)

一个集合分出两个子集,加法left集合和减法right集合 

left- right = target。

left + right = sum

right = sum - left

left - (sum - left) = target

left = (target + sum)/2

targe,sum是固定的,所以就可以转化为在集合nums中找出和为left的组合

class targetolution {
public:
    int findTargettargetumWays(vector<int>& nums, int target) {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) sum += nums[i];
        if (abs(target) > sum) return 0; 
        if ((target + sum) % 2 == 1) return 0; 
        int bagtargetize = (target + sum) / 2;
        vector<int> dp(bagtargetize + 1, 0);
        dp[0] = 1;
        for (int i = 0; i < nums.size(); i++) {
            for (int j = bagtargetize; j >= nums[i]; j--) {
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[bagtargetize];
    }
};

一和零

474. 一和零 - 力扣(LeetCode)

dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]

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);
     }
}

 完全背包

    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]);
        }
    }

 零钱兑换II

. - 力扣(LeetCode)

因为纯完全背包不在乎有没有顺序,有顺序也行没有顺序也行,但是这个题目的要求是没有顺序,求组合数,所以就要考虑for循环先后顺序调换有没有影响了

组合情况:先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况

for (int i = 0; i < coins.size(); i++) { // 遍历物品
    for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
        dp[j] += dp[j - coins[i]];
    }
}

排列数情况:背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。 

for (int j = 0; j <= amount; j++) { // 遍历背包容量
    for (int i = 0; i < coins.size(); i++) { // 遍历物品
        if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
    }
}

组合总和IV

. - 力扣(LeetCode)

if 语句的作用

确保在执行状态转移时不会访问不合法的索引,防止整数溢出。这是动态规划算法中常见的边界检查和安全性措施。

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

 零钱和

322. 零钱兑换 - 力扣(LeetCode)

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

单词拆分

139. 单词拆分 - 力扣(LeetCode)

要考虑for循环的先后顺序

lass Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
        vector<bool> dp(s.size() + 1, false);
        dp[0] = true;
        for (int j = 0; j < wordDict.size(); j++) { // 物品
            for (int i = wordDict[j].size(); i <= s.size(); i++) { // 背包
                string word = s.substr(i - wordDict[j].size(), wordDict[j].size());
                if ( word == wordDict[j] && dp[i - wordDict[j].size()]) {
                    dp[i] = true;
                }
            }
        }
        return dp[s.size()];

    }
};

打家劫舍

打家劫舍

相邻房间不能偷,考虑两种情况,偷或者不偷

198. 打家劫舍 - 力扣(LeetCode)

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

打家劫舍II

和上一题不同点:首尾相连连成环

情况如下

  1. 不考虑首尾元素,首尾元素连成环无影响
  2. 考虑首元素,默认没有尾元素,但头元素可选可不选
  3. 考虑尾元素,默认没有首元素,但尾元素可选可不选

情况2,3包含了情况1,所以分成两种情况,分别取两种情况的最大值

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        int result1 = robRange(nums, 0, nums.size() - 2); 
        int result2 = robRange(nums, 1, nums.size() - 1); 
        return max(result1, result2);
    }
    int robRange(vector<int>& nums, int start, int end) {
        if (end == start) return nums[start];
        vector<int> dp(nums.size());
        dp[start] = nums[start];
        dp[start + 1] = max(nums[start], nums[start + 1]);
        for (int i = start + 2; i <= end; i++) {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[end];
    }
};

 打家劫舍III

337. 打家劫舍 III - 力扣(LeetCode)

遍历方式:后序遍历

与前两题的不同点是这个是在二叉树的结构当中

class Solution {
public:
    int rob(TreeNode* root) {
        vector<int> result = robTree(root);
        return max(result[0], result[1]);//根节点投或者不投
    }
    // 长度为2的数组,0:不偷,1:偷
    vector<int> robTree(TreeNode* cur) {
        if (cur == NULL) return vector<int>{0, 0};
        vector<int> left = robTree(cur->left);//左子树
        vector<int> right = robTree(cur->right);//右子树
        // 偷cur,那么就不能偷左右节点。
        int val1 = cur->val + left[0] + right[0];
        // 不偷cur,那么可以偷也可以不偷左右节点,则取较大的情况
        int val2 = max(left[0], left[1]) + max(right[0], right[1]);
        return {val2, val1};
    }
};

买卖股票的最佳时机

买卖股票的最佳时机1

dp[i][0] :第i天持有股票所得最多现金

dp[i][1] :第i天不持有股票所得最多现金 

dp[i][0] = max(dp[i - 1][0], -prices[i]);

//第i-1天持有股票和第i天买入股票,这里可以看成是纯利润

dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);

//第i-1天就不持有股票/第i天出了股票

最后取得最大值的情况坑定是卖出股票的情况,也就是不持有股票的情况

class Solution {
public:
    int maxProfit(vector<int>& prices) {
       int len=prices.size();
       if(len==0)  return 0;
       vector<vector<int>>dp(len,vector<int>(2));
       dp[0][0]-=prices[0];
       dp[0][1]=0;
       for(int i=1;i<len;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[len - 1][1];
    }
};

 买卖股票的最佳时机II

122. 买卖股票的最佳时机 II - 力扣(LeetCode)

与上题的区别就是可以买卖多次,dp[i][0]递归方式有所变化

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

 买卖股票的最佳时机III

123. 买卖股票的最佳时机 III - 力扣(LeetCode)

与上两题的区别就是至多可以买卖两次,要分多种情况讨论

 多情况讨论

  1. 无操作
  2. 第一次持有股票
  3. 第一次不持有股票
  4. 第二次持有股票
  5. 第二次不持有股票
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if (prices.size() == 0) return 0;
        vector<vector<int>> dp(prices.size(), vector<int>(5, 0));
        dp[0][1] = -prices[0];
        dp[0][3] = -prices[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];
    }
};

买卖股票的最佳时机IV

188. 买卖股票的最佳时机 IV - 力扣(LeetCode)

与上两题的区别就是至多可以买卖K次,要分多种情况讨论

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {

        if (prices.size() == 0) return 0;
        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];
        }
        for (int i = 1;i < prices.size(); i++) {
            for (int j = 0; j < 2 * k - 1; j += 2) {
                dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
                dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
            }
        }
        return dp[prices.size() - 1][2 * k];
    }
};

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

多情况

  • 状态一:持有股票状态dp[i][0]
  • 不持有股票状态,这里就有两种卖出股票状态
    • 状态二:保持卖出股票的状态(前一天/两天卖出股票,没操作/度过一天冷冻期。dp[i][1])
    • 状态三:今天卖出的股票dp[i][2]
  • 状态四:冷冻期状态dp[i][3]
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if (n == 0) return 0;
        vector<vector<int>> dp(n, vector<int>(4, 0));
        dp[0][0] -= prices[0]; 
        for (int i = 1; i < n; 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][1], dp[i - 1][3]);
            dp[i][2] = dp[i - 1][0] + prices[i];
            dp[i][3] = dp[i - 1][2];
        }
        return max(dp[n - 1][3], max(dp[n - 1][1], dp[n - 1][2]));
    }
};

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

 和买卖股票的最佳时机II的区别就是有个手续费

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]);
    }
};

子序列问题

最长递增子序列

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);//长度要加1
            }
            if (dp[i] > result) result = dp[i]; 
        }
        return result;
    }
};

最长连续递增序列

674. 最长连续递增序列 - 力扣(LeetCode)

重点在连续

for (int i = 1; i < nums.size(); i++) {
     if (nums[i] > nums[i - 1]) { 
          dp[i] = dp[i - 1] + 1;
     }
  if (dp[i] > result) result = dp[i];
}
     

最长重复子数组

dp[i][j] :以下标i - 1为尾的nums1,和以j - 1为尾的nums的最长重复子数组长度

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 result = 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] > result) result = dp[i][j];
            }
        }
        return result;
    }
};

最长公共子序列

dp[i][j]:在区间[0, i - 1]的num1和区间[0, j - 1]的num2的最长公共子序列长度

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 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]);
                }
            }
        }
        return dp[text1.size()][text2.size()];
    }
};

不相交的线

1035. 不相交的线 - 力扣(LeetCode)

本质上就是找最长公共子序列

最大子序和

53. 最大子数组和 - 力扣(LeetCode)

dp[i]:以i结尾的(nums[i])的最大连续子序列和

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

编辑距离

判断子序列

392. 判断子序列 - 力扣(LeetCode)

s和t的最长公共子序列长度就等于最短的那个子序列的长度,就说明s是t的子序列

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;
        return false;
    }
};

不同的子序列

115. 不同的子序列 - 力扣(LeetCode)

递归顺序

原因可以看例子:因为考虑的是从s中删除元素会不会和t相等,所以如果s=“bagg”,t=“bag”,则t可以有s[0][1][2]和s[0][1][3]组成

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];
}

这里初始化有所变化,因为如果s中有元素,t中没元素的话就是有一个方案,如果t中有元素,s中没有元素方案个数为0 

代码 

class Solution {
public:
    int numDistinct(string s, string t) {
        vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1));
        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[i][j]:以i-1为尾的字符串word1和以j-1为尾的字符串word2相同的最小操作次数

class Solution {
public:
    int minDistance(string word1, string word2) {
        vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1));
        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(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
                }
            }
        }
        return dp[word1.size()][word2.size()];
    }
};

编辑距离 

72. 编辑距离 - 力扣(LeetCode)

让word1变成word2,通过增删改 

dp[i][j]:以i-1为尾的字符串word1和以j-1为尾的字符串word2相同的最小操作次数

理解:删除word1中的元素相当于添加word2中的元素

改的操作就相当于 dp[i][j] = dp[i - 1][j - 1] + 1;

class Solution {
public:
    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({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
                }
            }
        }
        return dp[word1.size()][word2.size()];
    }
}

回文子串 

647. 回文子串 - 力扣(LeetCode)

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

最长回文子序列

516. 最长回文子序列 - 力扣(LeetCode)

和上一题的最大区别是该题不强调连续性

注意i的遍历顺序,i是从下向上遍历的

注意for循环不可以颠倒,因为j是依赖于i的大小的

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() - 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 + 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[0][s.size() - 1];
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1698661.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

百亿级流量红包系统,如何做架构?(字节面试真题)

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的架构类/设计类的场景题&#xff1a; 1.如何设计高并发红包系统 &#xff0…

Linux 编译器gcc/g++使用

gcc/g同理 编译器运行过程 1. 预处理&#xff08;进行宏替换) gcc -E a.c -o a.i 预处理后还是c语言 -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面 告诉gcc&#xff0c;从现在开始进行程序的翻译&#xff0c;将预处理工作做完停下 2. 编译&#x…

电缆厂可视化:提升生产透明度与运营效率

图扑电缆厂可视化系统通过实时监控和数据分析&#xff0c;提高生产过程的透明度和可控性&#xff0c;优化资源配置和质量管理&#xff0c;显著提升运营效率和产品质量。

力扣739. 每日温度

Problem: 739. 每日温度 文章目录 题目描述思路复杂度Code 题目描述 思路 若本题目使用暴力法则会超时&#xff0c;故而使用单调栈解决&#xff1a; 1.创建结果数组res&#xff0c;和单调栈stack&#xff1b; 2.循环遍历数组temperatures&#xff1a; 2.1.若当stack不为空同时…

如何使用ssh将vscode 连接到服务器上,手把手指导

一、背景 我们在开发时&#xff0c;经常是window上安装一个vscode编辑器&#xff0c;去连接一个虚拟机上的linux&#xff0c;这里常用的是SSH协议&#xff0c;了解其中的操作非常必要。 二、SSH协议 SSH&#xff08;Secure Shell&#xff09;是一种安全协议&#xff0c;用于…

贝叶斯算法:机器学习中的“黄金法则”与性能提升之道

&#x1f440;传送门&#x1f440; &#x1f50d;机器学习概述&#x1f340;贝叶斯算法原理&#x1f680;贝叶斯算法的应用✨文本分类✨医疗系统 &#x1f496;贝叶斯算法优化✨贝叶斯算法优化的主要步骤✨贝叶斯算法优化的优点✨贝叶斯算法优化的局限性 &#x1f697;贝叶斯算…

【OceanBase诊断调优】—— 排查 IO 问题的方法

本文主要介绍 OceanBase 数据库 V4.x 版本中排查 IO 问题的方法以及 IO 相关的日志和视图。 IO 相关问题 -4013 内存爆、IoControl 模块内存泄漏 目前 IO 内存爆可能的原因如下&#xff0c;及相应的排查方法。 其他模块使用 IO 内存后未释放导致泄漏。 日志分析。 通过关键词…

kubectl自动补全插件

1. 安装bash completion yum install -y bash-completion 2. 修改配置补全脚本 在文件 ~/.bashrc 中导入(source)补全脚本: echo source <(kubectl completion bash) >> ~/.bashrc 将补全脚本添加到目录 /etc/bash_completion.d中: kubectl completion bash >…

vivado 设计连接性

设计连接性 IP集成商提供设计师协助&#xff0c;帮助您完成连接过程 设计。图3显示了MHS的一个示例&#xff0c;图4显示了设计帮助 可在IP集成商中获得 地址映射 在XPS中&#xff0c;无论主机访问从机IP&#xff0c;每个从机都有相同的地址。IP integrator为基于master的寻址提…

Wpf 使用 Prism 实战开发Day25

首页待办事项及备忘录添加功能 一.修改待办事项和备忘录逻辑处理类,即AddMemoViewModel和AddTodoViewModel 1.AddMemoViewModel 逻辑处理类&#xff0c;添加View视图数据要绑定的实体类 Model public class AddMemoViewModel :BindableBase,IDialogHostAware{public AddMemoV…

基于信号分解方法的机械故障诊断方法存在的问题

一方面&#xff0c;由于结构共振、测试噪声的干扰&#xff0c;为了确保分解精度&#xff0c;需要给定准确的参数初值(例如&#xff0c;瞬时频率)。研究人员通常认为零部件特征频率与通过传动比和驱动转速计算的理论值基本吻合&#xff0c;并基于理论值设置参数初值。事实上&…

matlab使用教程(78)—控制颜色图范围

1.控制颜色图范围 对于您创建的许多类型的可视化图形&#xff0c;MATLAB 默认将完整的数据范围映射到颜色图上。数据中的最小值映射到颜色图中的第一行&#xff0c;最大值映射到颜色图中的最后一行。所有中间值线性映射到颜色图的中间行。 这种默认映射适用于大部分情况&#x…

网络安全从入门到精通(特别篇I):应急响应之不同平台后门排查思路

Windows-后门-常规&权限维持&内存马 Linux-后门-常规&权限维持&Rootkit&内存马 Windows实验 1、常规MSF后门-分析检测 2、权限维持后门-分析检测 3、Web程序内存马-分析检测 常见工具集合: https://mp.weixin.qq.com/s/L3Lv06bFdUX_ZE4rS69aDg 常规…

【知识拓展】LocalTunnel-高性价比的内网穿透工具(2)

前言 上一篇通过ngrok进行内网穿透&#xff0c;有几个问题&#xff1a; ①需要注册&#xff0c;而且注册需要科学上网&#xff0c;相对麻烦 ②安装配置相对麻烦&#xff0c;authtoekn有限制 上述相对&#xff0c;指的是在非生产环境中做一个简单内网穿透&#xff0c;相对于…

京东应届生公司内网说了一句‘什么时候被pdd收购‘,结果惨遭辞退

京东应届生公司内网说了一句’什么时候被pdd收购’&#xff0c;结果惨遭公司开除 这个事最近在圈子讨论比较多 前二天&#xff0c;有一个上海交大毕业的应届生&#xff0c;在京东实习了9个月&#xff0c;好不容易转正12天后&#xff0c;只因在内网说了一句话&#xff0c;就被…

为什么本科毕业后我坚定地选择了就业而不是考研?

大家好&#xff0c;我是小布丁。今天来聊聊我为什么本科毕业后选择了就业而不是考研。 在整个大学期间&#xff0c;我被亲戚拷问最多的问题就是&#xff1a;准备考研吗&#xff1f;相信很多大学生都遇到过这种情况吧。 如果你说准备还好&#xff0c;亲戚大概率就不会问下去&a…

wetool企业版使用教程及下载方式 微兔该如何使用 wetool还能用吗 wetool扳手工具wetool操作方法难吗 wetool有哪些功能

今天给大家推荐一款我们目前在使用的电脑群发工具掘金小蜜&#xff0c;不仅可以无限多开&#xff0c;方便你同时管理多个账号&#xff0c;群发功能更是十分强大&#xff0c;轻松释放你的双手。 掘金小蜜&#xff08;只支持Win7及以上操作系统&#xff0c;没有推Mac版和手机客户…

蜗牛星际无法用ventoy安装FreeBSD14.1

ventoy 是一个非常棒的开源工具&#xff0c;可以制作usb启动盘&#xff0c;而且可以制作多系统启动盘&#xff0c;只要把iso文件放到ventoy盘的根目录就可以了。 一台蜗牛星际的老机器&#xff0c;把mini ssd盘从16G 升级到32G&#xff0c;用ventoy安装FreeBSD的时候失败&…

【题目】2023年全国职业院校技能大赛 GZ073 网络系统管理赛项赛题第4套B模块

2023年全国职业院校技能大赛 GZ073网络系统管理赛项 赛题第4套 模块B&#xff1a;服务部署 Windows初始化环境 **&#xff08;一&#xff09;默认账号及默认密码** ----------------------------Username: AdministratorPassword: ChinaSkill23!Username: demoPassword: Chi…

【深度学习基础】NumPy数组库的使用

目录 写在开头 一、数组的类型与维度 数组的类型 数组的维度 二、数组的创建 递增数组 同值数组 随机数数组 三、数组的索引 访问/修改单个元素 花式索引 数组的切片 四、数组的变形 数组的转置 数组的翻转 数组的形状改变 数组的拼接 五、数组的运算 数…