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

前言

更详细的在大佬的代码随想录 (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.mzph.cn/diannao/16608.shtml

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

相关文章

乐理学习-音及音名

1. 我觉得练习题很重要。我要得到一个反馈 所以我想没学习完书中的一节就要把练习题做下来&#xff0c;虽然慢点也可以。 2. 做个小计划。 今天计算了一下学完《基本乐理-李重光》如果每天3张。也要80天干完&#xff0c;希望能有一天可以学习7张的速度的时候。 3. 练习记录…

STM32H7系统窗口看门狗 (WWDG)应用方法介绍

目录 概述 1 认识窗口看门狗 (WWDG) 1.1 窗口看门狗定义 1.2 WWDG 主要特性 2 WWDG 功能说明 2.1 WWDG框图 2.2 WWDG 内部信号 2.3 控制递减计数器 2.4 看门狗中断高级特性 2.5 如何设置看门狗超时 3 WWDG 寄存器 3.1 控制寄存器 (WWDG_CR) 3.2 配置寄存器 (W…

MicroLED:苹果对知识产权的影响

Yole的洞察揭示&#xff0c;MicroLED IP在经历了七年的爆炸式增长后&#xff0c;已然屹立于行业之巅。苹果公司&#xff0c;作为微LED领域的先行者&#xff0c;早在2014年便敏锐地捕捉到Luxvue这家初创公司的潜力&#xff0c;将其纳入麾下&#xff0c;引发了业界的广泛关注。然…

【Springboot系列】SpringBoot 中的日志如何工作的,看完这一篇就够了

文章目录 强烈推荐引言Spring Boot 中的日志是怎么工作日志框架选择配置文件日志级别自定义日志配置集成第三方日志库实时监控和日志管理 Log4j2工作原理分析1. 核心组件2. 配置文件3. Logger的继承和层次结构4. 日志事件处理流程5. 异步日志 总结强烈推荐专栏集锦写在最后 强烈…

每日练习之矩阵乘法——斐波那契公约数

斐波那契公约数 题目描述 运行代码 #include <iostream> #include <vector> using namespace std; const long long mod 1000000007;// 矩阵乘法函数 vector<vector<long long>> matrixMultiply(const vector<vector<long long>>& …

移动端特效

一&#xff0c;触屏事件 1.触屏touch事件 2.触摸事件对象 如果侦听的是一个DOM元素&#xff0c;他俩是一样的 如果手指离开了&#xff0c;不能侦听到前两者&#xff0c;能侦听到第三个 我们一般都是触摸元素&#xff0c;所以一般都是第二个比较常用 第一个&#xff1a;屏幕…

Leetcode 力扣92. 反转链表 II (抖音号:708231408)

给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4 输出&#xff1a;[1,4,3,2…

简单的基于信号处理的心电信号ECG特征波分割方法(MATLAB)

正常的心电图中&#xff0c;每个心跳周期内包含三个主要的特征波&#xff1a;&#xff30;波、QRS波和&#xff34;波&#xff0c;如下图所示。心电特征波能够反映心脏的生理状态信息&#xff0c;通过对其形状、幅值和持续时间的分析&#xff0c;可以用来辅助诊断心血管疾病。对…

vs2019 c++20 规范的 STL 库的智能指针 shared、unique 、weak 及 make_** 函数的源码注释汇总,和几个结论

智能指针的源码都在 《memory》 头文件中。因为头文件太长&#xff0c;再者本次整理是基于以前的零散的模板分析。故相当于抽取了该头文件中关于智能指针的源码进行分析&#xff0c;注释。 &#xff08;1 探讨一&#xff09;当独占指针指向数组时&#xff0c;其默认的删除器是…

mysql表约束基础 【default | 主键 | 外键 | 唯一键】

博客主页&#xff1a;花果山~程序猿-CSDN博客 文章分栏&#xff1a;MySQL之旅_花果山~程序猿的博客-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能吧&#xff01;让我们一起努力&#xff0c;一起成长&#xff01; 目录 一&#xff0c;表的约…

vue3快速入门(局部使用)

目录 前置知识JavaScript-导入导出 入门操作 变量渲染页面 局部使用vue的实现步骤 vue指令 v-for v-bind v-if v-show v-on v-model 生命周期 前置知识JavaScript-导入导出 正常情况在html导入js文件是全部导入&#xff0c;这样会导致性能上的损失 。 JS提供的…

STM32F1之OV7725摄像头·SCCB总线代码编写附带源码详解

STM32F1之OV7725摄像头-CSDN博客 STM32F1之OV7725摄像头像素数据输出时序、FIFO 读写时序以及摄像头的驱动原理详解-CSDN博客 目录 1. 硬件设计 1.1 SCCB 控制相关 1.2 VGA 时序相关 1.3 FIFO 相关 1.4 XCLK 信号 2. 代码设计 2.1 SCCB总线软件实现 2.1.1 宏定…

AI图书推荐:ChatGPT解码—人工智能增强生活指南

《ChatGPT解码—人工智能增强生活指南》&#xff08;ChatGPT Decoded. A Beginners Guide to AI-Enhanced Living &#xff09;是一本由 大卫维恩斯&#xff08;David Wiens &#xff09;所著的书籍&#xff0c;旨在帮助读者了解并有效利用GPT-4语言模型这一强大工具来提升日常…

springmvc Web上下文初始化

Web上下文初始化 web上下文与SerlvetContext的生命周期应该是相同的&#xff0c;springmvc中的web上下文初始化是由ContextLoaderListener来启动的 web上下文初始化流程 在web.xml中配置ContextLoaderListener <listener> <listener-class>org.springframework.…

ncnn 优化量化

问题&#xff1a;发现推理时间过长&#xff0c;需要优化 当前正在做人脸检测部署&#xff0c;发现检测速度有点吓人&#xff0c;以下监测的时间 gpu&#xff1a; cpu&#xff1a; gpu推理大概整体时间200多毫秒&#xff0c;cpu推理时间300多毫秒&#xff0c;这里暂时没去考虑…

Oracle实践|内置函数之字符串函数

&#x1f4eb; 作者简介&#xff1a;「六月暴雪飞梨花」&#xff0c;专注于研究Java&#xff0c;就职于科技型公司后端工程师 &#x1f3c6; 近期荣誉&#xff1a;华为云云享专家、阿里云专家博主、腾讯云优秀创作者、ACDU成员 &#x1f525; 三连支持&#xff1a;欢迎 ❤️关注…

C++模板——函数模板和类模板

目录 泛型编程 函数模板 函数模板概念 函数模板的定义和语法 函数模板的工作原理 函数模板的实例化 隐式实例化 显示实例化 函数模板的匹配原则 类模板 类模板的定义格式 类模板的实例化 泛型编程 什么是泛型编程&#xff1f; 泛型编程&#xff08;Generic Pr…

【Linux-并发与竞争】

Linux-并发与竞争 ■ 原子操作■ 原子操作简介■ 原子整形操作 API 函数■ 原子位操作 API 函数■ 示例一&#xff1a;原子操作实验&#xff0c;使用原子变量来实现对实现设备的互斥访问 ■ 自旋锁■ 自旋锁 API 函数■ 死锁■ 最好的解决死锁方法就是获取锁之前关闭本地中断&a…

LeetCode 124 —— 二叉树中的最大路径和

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 二叉树的问题首先我们要想想是否能用递归来解决&#xff0c;本题也不例外&#xff0c;而递归的关键是找到子问题。 我们首先来看看一棵最简单的树&#xff0c;也就是示例 1。这样的一棵树总共有六条路径&#xf…

docker如何拉取nginx最新镜像并运行

要拉取Docker Hub上的最新Nginx镜像&#xff0c;您可以使用以下命令&#xff1a; docker pull nginx 这个命令会从Docker Hub下载最新版本的Nginx镜像。如果您想要拉取特定版本的Nginx镜像&#xff0c;可以指定版本号&#xff0c;例如&#xff1a; docker pull nginx:1.18.0 拉…