理论基础

  1. 确定dp数组(dp table)以及下标的含义

  1. 确定递推公式

  1. dp数组如何初始化

  1. 确定遍历顺序

  1. 举例推导dp数组

基础题目

斐波那契数量:力扣509

  1. 确定dp数组(dp table)以及下标的含义:第i个斐波那契数的数值为dp[i]

  1. 确定递推公式:dp[i]=dp[i-1]+dp[i-2]

  1. dp数组如何初始化:dp[0]=0,dp[1]=1(题目中给出)

  1. 确定遍历顺序:从前向后

  1. 举例推导dp数组

class Solution {
public:int fib(int n) {if(n<=1) return n;vector<int> dp(n+1);//0~n个数字dp[0]=0;dp[1]=1;//初始化for(int i=2;i<=n;i++){dp[i]=dp[i-1]+dp[i-2];//递推公式}return dp[n];}
};

*别忘了边界条件的判断

爬楼梯:力扣70

  1. 确定dp数组(dp table)以及下标的含义:达到i阶楼梯有几种方法

  1. 确定递推公式:dp[i]=dp[i-1]+dp[i-2]

  1. dp数组如何初始化:dp[0]没有意义,dp[1]=1,dp[2]=2(题目中给出)

  1. 确定遍历顺序:由前向后

  1. 举例推导dp数组

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

使用最小花费爬楼梯:力扣746

  1. 确定dp数组(dp table)以及下标的含义:达到下标i的位置所需要的花费为dp[i]

  1. 确定递推公式:dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])

  1. dp数组如何初始化:(题目中需要向上跳跃才有体力值)dp[1]=0,dp[0]=0

  1. 确定遍历顺序:由前向后

  1. 举例推导dp数组

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {vector<int> dp(cost.size()+1);//0~n个数字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

  1. 确定dp数组(dp table)以及下标的含义:从(0,0)走到(i,j)有几种不同的走法

  1. 确定递推公式:只允许向右和向下走 dp[i][j]=dp[i-1][j]+dp[i][j-1]

  1. dp数组如何初始化:第零行、第零列为1

  1. 确定遍历顺序:由左到右、由上到下

  1. 举例推导dp数组

class Solution {
public:int uniquePaths(int m, int n) { //m几行 n几列vector<vector<int>> dp(m, vector<int>(n, 0));//初始化 第零行第零列是1 表示有一种方法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];}
};

不同路径2:力扣63

**同样的方法,只不过增加了障碍,就无法通过。因此,障碍后的路径都为0

  1. 确定dp数组(dp table)以及下标的含义:从(0,0)走到(i,j)有几种不同的走法

  1. 确定递推公式:只允许向右和向下走 dp[i][j]=dp[i-1][j]+dp[i][j-1] 如果有障碍就走不了;没有障碍进行递推

  1. dp数组如何初始化:第零行、第零列为1,如遇到障碍,障碍后均为0(表示不能通过)

  1. 确定遍历顺序:由左到右、由上到下

  1. 举例推导dp数组

class Solution {
public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int m=obstacleGrid.size();int n=obstacleGrid[0].size();if(obstacleGrid[0][0]==1||obstacleGrid[m-1][n-1]==1) return 0;vector<vector<int>> dp(m, vector<int>(n, 0));//注意特殊条件:如果在终点或起点出现障碍 白搭直接返回//初始化 第零行第零列是1 表示有一种方法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;}for(int i=1;i<m;i++){for(int j=1;j<n;j++){if(obstacleGrid[i][j]==1) continue;//如果碰到障碍物--此路不通--走不了dp[i][j]=dp[i-1][j]+dp[i][j-1];}}return dp[m-1][n-1];}
};

整数拆分:力扣343

  1. 确定dp数组(dp table)以及下标的含义:对i进行拆分得到的最大乘积dp[i]

  1. 确定递推公式:dp[i]=j*dp[i-j]

  1. dp数组如何初始化:dp[0]=0,dp[1]=0没有意义,dp[2]=1

  1. 确定遍历顺序:由小到大

  1. 举例推导dp数组

class Solution {
public:int integerBreak(int n) {vector<int> dp(n+1);//从0~n个数 有n+1//初始化dp[0]=0;dp[1]=0;dp[2]=1;for(int i=3;i<=n;i++){//从3开始for(int j=1;j<=i/2;j++){dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]));//j*(i-j)两个数 j*dp[i-j]三个以及以上的数}}return dp[n];}
};

*比较好理解,但是感觉比上面的题目难了不少

不同的二叉搜索树:力扣96

  1. 确定dp数组(dp table)以及下标的含义:有几种不同的二叉搜索树

  1. 确定递推公式:dp[i]+=dp[j-1]*dp[i-j] j为1~i的所有情况

  1. dp数组如何初始化:dp[0]=1(有一棵树)dp[1]=1(有一棵树)

  1. 确定遍历顺序:由小到大

  1. 举例推导dp数组

class Solution {
public:int numTrees(int n) {vector<int> dp(n+1);//0~n一共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]; //j-1左子树的个数 i-j是右子树的个数}}return dp[n];}
};

**对代码仍有不理解的地方,有时间再看一遍讲解

背包问题

01背包

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

01背包理论基础

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

  1. 确定递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); (不放物品i和放物品i)

  1. dp数组如何初始化:背包容量j为0的话,即dp[i][0]=0;dp[0][j]即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值:①当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小;②j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品;其他下标初始为什么数值都可以,因为都会被覆盖

  1. 确定遍历顺序:先遍历 物品还是先遍历背包重量都可以

  1. 举例推导dp数组

#include<iostream>
#include<vector>
using namespace std;void test01bagproblem(){vector<int> weight = {1, 3, 4};vector<int> value = {15, 20, 30};int bagweight = 4;// 二维dp数组vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));//初始化 行for(int j=0;j<=bagweight;j++){if(weight[0]>j) dp[0][j]=0;else dp[0][j]=value[0];}// weight数组的大小 就是物品个数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]);}}cout << dp[weight.size() - 1][bagweight] << endl;}int main(){test01bagproblem();
}

01背包理论基础(滚动数组)

将二维数组变为一维数组

  1. 确定dp数组(dp table)以及下标的含义:dp[j] 容量为j的背包,所背的物品价值可以最大为dp[j]。

  1. 确定递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); (不放物品i和放物品i)

  1. dp数组如何初始化:dp[0=0,因为背包容量为0所背的物品的最大价值就是0。其余初始化为0

  1. 确定遍历顺序:背包从大到小、先遍历物品后遍历背包顺序不可以变动

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]);}
}
  1. 举例推导dp数组

#include<iostream>
#include<vector>
using namespace std;void test_1_wei_bag_problem() {vector<int> weight = {1, 3, 4};vector<int> value = {15, 20, 30};int bagWeight = 4;// 初始化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]);}}cout << dp[bagWeight] << endl;
}int main() {test_1_wei_bag_problem();
}

分割等和子集:力扣0416

  1. 确定dp数组(dp table)以及下标的含义:背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]。当 dp[target] == target 的时候,背包就装满了。

  1. 确定递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);

  1. dp数组如何初始化:均初始化为0

  1. 确定遍历顺序

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]);}
}
  1. 举例推导dp数组

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 target=sum/2;// dp[i]中的i表示背包内总和// 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200// 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了vector<int> dp(10001,0);// 开始 01背包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]);}}// 集合中的元素正好可以凑成总和targetif(dp[target]==target) return true;return false;}
};

最后一块石头的重量2:力扣1049

  1. 确定dp数组(dp table)以及下标的含义:背量为j的背包最大的重量为dp[j]

  1. 确定递推公式:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);

  1. dp数组如何初始化:均为0

  1. 确定遍历顺序

for (int i = 0; i < stones.size(); i++) { // 遍历物品for (int j = target; j >= stones[i]; j--) { // 遍历背包dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);}
}
  1. 举例推导dp数组

class Solution {
public:int lastStoneWeightII(vector<int>& stones) {int sum=0;vector<int> dp(15000,0);//初始化for(int i=0;i<stones.size();i++){sum+=stones[i];}int target=sum/2;//会向下取整//开始动态规划for(int i=0;i<stones.size();i++){for(int j=target;j>=stones[i];j--){dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);}}return sum-dp[target]-dp[target]; //sum-dp[target]是剩余的部分 差值是最后的重量}
};

目标和:力扣0494

left组合 - right组合 = target

left + right = sum

left - (sum - left) = target 推导出 left = (target + sum)/2

target固定,sum固定,left可以求出来

问题就是在集合nums中找出和为left的组合

  1. 确定dp数组(dp table)以及下标的含义:填满j(包括j)这么大容积的包,有dp[j]种方法

  1. 确定递推公式:

dp[j] += dp[j - nums[i]]
  1. dp数组如何初始化:dp[0]=1,其余均初始化为0

  1. 确定遍历顺序

 for(int i=0;i<nums.size();i++){for(int j=bagsize;j>=nums[i];j--){dp[j]+=dp[j-nums[i]];}}
  1. 举例推导dp数组

class Solution {
public:int findTargetSumWays(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;//S的绝对值已经大于sumif((target+sum)%2==1) return 0;//sum 是5,S是2的话其实就是无解int bagsize=(sum+target)/2;vector<int> dp(bagsize+1,0);//初始化为0dp[0]=1;//答案为0的时候有一种方法//动态规划求解的情况for(int i=0;i<nums.size();i++){for(int j=bagsize;j>=nums[i];j--){dp[j]+=dp[j-nums[i]];}}return dp[bagsize];}
};

*有不明白的地方

一和零:力扣0474

m 和 n相当于是一个背包,两个维度的背包

  1. 确定dp数组(dp table)以及下标的含义:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。最终结果是dp[m][n]

  1. 确定递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);

  1. dp数组如何初始化:初始化为0

  1. 确定遍历顺序:一定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历

  1. 举例推导dp数组

class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {vector<vector<int>> dp(m + 1, vector<int> (n + 1, 0)); // 默认初始化0for(string str:strs){//遍历物品int x=0;int y=0;for(char c:str){if(c=='0') x++;else y++;}//遍历背包for(int i=m;i>=x;i--){for(int j=n;j>=y;j--){dp[i][j]=max(dp[i][j], dp[i - x][j - y] + 1);}}}return dp[m][n];}
};

*有不明白的地方

完全背包

完全背包又是也是01背包稍作变化而来,即:完全背包的物品数量是无限的。

有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

完全背包理论基础

完全背包的物品是可以添加多次的,所以要从小到大去遍历

物品在外层循环,遍历背包容量在内层循环

// 先遍历物品,再遍历背包
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]);}
}
void test_CompletePack() {vector<int> weight = {1, 3, 4};vector<int> value = {15, 20, 30};int bagWeight = 4;vector<int> dp(bagWeight + 1, 0);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]);}}cout << dp[bagWeight] << endl;
}
int main() {test_CompletePack();
}

零钱兑换2:力扣0518

钱币数量不限,就知道这是一个完全背包

组合数:组合不强调元素之间的顺序,排列强调元素之间的顺序

  1. 确定dp数组(dp table)以及下标的含义:装满容量为j的背包有dp[j]种方法

  1. 确定递推公式:dp[j] += dp[j - coins[i]](求装满背包有几种方法,公式都是:dp[j] += dp[j - nums[i]])

  1. dp数组如何初始化:dp[0] = 1,其余初始化为0

  1. 确定遍历顺序

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

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

组合数:外层for循环遍历物品,内层for遍历背包。

排列数:外层for遍历背包,内层for循环遍历物品。

组合总和4:力扣0377

排列数:排列强调顺序,(1,5)和(5,1)是两个不同的排列

  1. 确定dp数组(dp table)以及下标的含义:装满容量为j的背包有dp[j]种方法

  1. 确定递推公式:dp[j] += dp[j - coins[i]](求装满背包有几种方法,公式都是:dp[j] += dp[j - nums[i]])

  1. dp数组如何初始化:dp[0] = 1,其余初始化为0

  1. 确定遍历顺序:先遍历背包再遍历物品

  1. 举例推导dp数组

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]>=0&&dp[j] < INT_MAX - dp[j - nums[i]]) //C++测试用例有两个数相加超过int的数据,所以需要在if里加上dp[i] < INT_MAX - dp[i - num]dp[j]+=dp[j-nums[i]];}}return dp[target];}
};

爬楼梯:力扣0070

零钱兑换:力扣0322

  1. 确定dp数组(dp table)以及下标的含义:凑足总额为j所需钱币的最少个数为dp[j]

  1. 确定递推公式:dp[j] =min(dp[j - coins[i]] + 1, dp[j]);

  1. dp数组如何初始化:凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;因为要取得都是最小值,所以初始化为int的最大值

  1. 确定遍历顺序:不强调集合是组合还是排列

  1. 举例推导dp数组

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(j-coins[i]>=0&&dp[j-coins[i]]!=INT_MAX)dp[j]=min(dp[j],dp[j-coins[i]]+1);}}if(dp[amount]==INT_MAX) return -1;return dp[amount];}
};

完全平方数:力扣0279

  1. 确定dp数组(dp table)以及下标的含义:和为j的完全平方数的最少数量为dp[j]

  1. 确定递推公式:dp[j] = min(dp[j - i * i] + 1, dp[j]);

  1. dp数组如何初始化:dp[0]=0,其余均为最大值这样再取最小值的时候才不会覆盖

  1. 确定遍历顺序:不强调集合是组合还是排列

  1. 举例推导dp数组

class Solution {
public:int numSquares(int n) {vector<int> dp(n+1,INT_MAX);dp[0]=0;//初始化for(int i=0;i*i<=n;i++){//遍历物品for(int j=i*i;j<=n;j++){//遍历背包if(j-i*i>=0&&dp[j-i*i]!=INT_MAX)dp[j]=min(dp[j],dp[j-i*i]+1);}}if(dp[n]==INT_MAX) return -1;return dp[n];}
};

单词拆分:力扣0139

  1. 确定dp数组(dp table)以及下标的含义:字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词

  1. 确定递推公式:如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true。(j < i )

  1. dp数组如何初始化:dp[0]=true

  1. 确定遍历顺序:这里是求排列数,有一定的顺序要求,需要先遍历背包再遍历物品。

  1. 举例推导dp数组

class 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 i=1;i<=s.size();i++){//遍历背包for(int j=0;j<i;j++){//遍历物品string word=s.substr(j,i-j);if(wordSet.find(word)!=wordSet.end()&&dp[j]==true){dp[i]=true;}}}return dp[s.size()];}
};

多重背包

有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。

多重背包理论基础

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

总结

递推公式

问能否能装满背包(或者最多装多少):dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])

问装满背包有几种方法:dp[j] += dp[j - nums[i]]

问背包装满最大价值:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

问装满背包所有物品的最小个数:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);

遍历顺序

01背包

二维dp数组01背包先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历。

一维dp数组01背包只能先遍历物品再遍历背包容量,且第二层for循环是从大到小遍历

完全背包

纯完全背包的一维dp数组实现,先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历

求组合数就是外层for循环遍历物品,内层for遍历背包

求排列数就是外层for遍历背包,内层for循环遍历物品

打家劫舍

打家劫舍:力扣198

  1. 确定dp数组(dp table)以及下标的含义:考虑下标i,偷的最大的金币数为dp[i],答案是dp[nums.size()-1]

  1. 确定递推公式:dp[i]=max(dp[i-1],dp[i-2]+nums[i])

  1. dp数组如何初始化:dp[0]=nums[0] dp[1]=max(nums[0],nums[1]),其余下标

  1. 确定遍历顺序:顺序遍历;for(i=2;i<nums.size();i++)

  1. 举例推导dp数组

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

打家劫舍2:力扣213

区别:连城环了 首尾是挨着的

三种情况:①首尾都不选②选首元素③选尾元素==》取②③的最大值

  1. 确定dp数组(dp table)以及下标的含义:偷的最大的金币数为dp[i],答案是dp[nums.size()-1]

  1. 确定递推公式:dp[i]=max(dp[i-1],dp[i-2]+nums[i])

  1. dp数组如何初始化:dp[0]=nums[0] dp[1]=max(nums[0],nums[1]),其余下标

  1. 确定遍历顺序:顺序遍历;for(i=2;i<nums.size();i++)

  1. 举例推导dp数组

class Solution {
public:int rob1(vector<int>& nums,int start,int end) {vector<int> dp(nums.size());if(start==end) return nums[start];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];}int rob(vector<int>& nums) {//!!第一个房屋和最后一个房屋是紧挨着的//分为两种情况:①包含首元素 ②不包含首元素包含尾元素if(nums.size()==0) return 0;if(nums.size()==1) return nums[0];int result1=rob1(nums,0,nums.size()-2);int result2=rob1(nums,1,nums.size()-1);return max(result1,result2);}
};

打家劫舍3:力扣337

区别:是二叉树形式

  1. 确定dp数组(dp table)以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。

  1. 确定递推公式:偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0]; 不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);最后当前节点的状态就是{val2, val1}; 即:{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}

  1. dp数组如何初始化:确定终止条件在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回

  1. 确定遍历顺序:后序遍历。 因为要通过递归函数的返回值来做下一步计算。通过递归左节点,得到左节点偷与不偷的金钱。通过递归右节点,得到右节点偷与不偷的金钱。

  1. 举例推导dp数组

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<int> robTree(TreeNode *cur){//终止条件if(cur==NULL) return vector<int> {0,0};//遍历顺序 做 右 中的后序遍历//递归左节点vector<int> leftdp=robTree(cur->left);//递归右结点vector<int> rightdp=robTree(cur->right);//单层递归逻辑//偷当前结点 左右孩子不偷int val1=cur->val+leftdp[0]+rightdp[0];//不偷当前结点 判断左右孩子大小 偷大的int val2=max(leftdp[0],leftdp[1])+max(rightdp[0],rightdp[1]);return vector<int> {val2,val1};}//主函数int rob(TreeNode* root) {vector<int> result=robTree(root);return max(result[0],result[1]);}
};

股票问题

买卖股票的最佳时机(只能买卖一次):力扣121

  1. 确定dp数组(dp table)以及下标的含义:dp[i][0]持有股票的最大现金,dp[i][0]不持有股票的最大现金;结果:max(dp[size-1][0],dp[size-1][1]) **持有表示已经有,不一定是当天才买这个股票

  1. 递推公式:dp[i][0]=max(-dp[i-1][0] -price[i]);dp[i][1]=max(dp[i-1][1],dp[i-1][0]+price[i])

  1. dp数组如何初始化:dp[0][0]=-price[0];dp[0][1]=0

  1. 确定遍历顺序:从前往后遍历---for(i=1;i<len;i++){}

  1. 举例推导dp数组

class Solution {
public:int maxProfit(vector<int>& prices) {int a=prices[0];vector<vector<int>> dp(prices.size(), vector<int>(2));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],dp[i][0]+prices[i]);//不持有}return dp[prices.size()-1][1];}
};

买卖股票的最佳时机2(可以买卖多次):力扣122

一只股票可以买卖多次,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润

  1. 确定dp数组(dp table)以及下标的含义:dp[i][0] 表示第i天持有股票所得现金。dp[i][1] 表示第i天不持有股票所得最多现金

  1. 递推公式:dp[i][0]=max(-dp[i-1][0],dp[i-1][1]-price[i]);dp[i][1]=max(dp[i-1][1],dp[i-1][0]+price[i])

  1. dp数组如何初始化:

  1. 确定遍历顺序:

  1. 举例推导dp数组

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

买卖股票的最佳时机3(最多买卖两次):力扣123

至多买卖两次,那么是买一次还是买两次呢??

  1. 确定dp数组(dp table)以及下标的含义:dp[i][0] 不操作;dp[i][1]第一次持有;dp[i][2]第一次不持有;dp[i][3]第二次持有;dp[i][4]第二次不持有

  1. 递推公式:

dp[i][0]=dp[i-1][0]; 就相当于是0

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

dp[i][2]=max(dp[i-1][2],dp[i-1][1]+price[i]);

dp[i][3]=max(dp[i-1][3],dp[i-1][2]-price[i]);

dp[i][4]=max(dp[i-1][4],dp[i-1][3]+price[i]);

  1. dp数组如何初始化:dp[0][0]=0 dp[0][1]=-price[0] dp[0][2]=0 dp[0][3]=-price[0] dp[0][4]=0

  1. 确定遍历顺序:for(i=1;i<price.size();i++ ){} 最终dp[price.size()-1][4]

  1. 举例推导dp数组

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][0]=0;//不持有dp[0][1]=-prices[0];//第一次持有dp[0][2]=0;//第一次不持有dp[0][3]=-prices[0];//第二次持有dp[0][4]=0;//第二次不持有for(int j=1;j<prices.size();j++){dp[j][0]=dp[j-1][0];dp[j][1]=max(dp[j-1][1],dp[j-1][0]-prices[j]);dp[j][2]=max(dp[j-1][2],dp[j-1][1]+prices[j]);dp[j][3]=max(dp[j-1][3],dp[j-1][2]-prices[j]);dp[j][4]=max(dp[j-1][4],dp[j-1][3]+prices[j]);}return dp[prices.size()-1][4];}
};

买卖股票的最佳时机4(最多买卖k次):力扣188

买卖股票k次,第一个维度是股票第二维度是次数2k

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j] i:第几天 j:第几次买卖

  1. 递推公式:

dp[i][0]=dp[i-1][0]; 就相当于是0

dp[i][1]=max(dp[i-1][1],dp[i-1][0]-price[i]); //第一次买

dp[i][2]=max(dp[i-1][2],dp[i-1][1]+price[i]);//第一次卖

dp[i][3]=max(dp[i-1][3],dp[i-1][2]-price[i]); //第二次买

dp[i][4]=max(dp[i-1][4],dp[i-1][3]+price[i]); //第二次卖

  1. dp数组如何初始化:for(j=1;j<2*k;j+=2){dp[0][j]=-price[0]}

  1. 确定遍历顺序:for(j=0;j<2*k;j+=2){dp[i][j+1]=max(dp[i-1][j+1],dp[i-1][j]-price[i]);dp[i][j+2]=max(dp[i-1][j+2],dp[i][j+1]+price[i])}

  1. 举例推导dp数组

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

最佳买卖股票时机含冷冻期(买卖多次,卖出有一天冷冻期):力扣309

  1. 确定dp数组(dp table)以及下标的含义:dp[i][0]持股;dp[i][1]保持卖出股票的状态;dp[i][2] 真正卖出股票的状态;dp[i][3]冷冻期

  1. 递推公式:

dp[i][0]=max(dp[i-1][0]//持有 dp[i-1][3]-price[i],dp[i-1][1]//买入股票 前一天是冷冻期or前一天是保持卖出股票状态)

dp[i][1]=dp[i-1][1]//保持卖出股票状态dp[i-1][3]//冷冻期下一天

dp[i][2]=dp[i-1][1]+price[i]//前一天持有股票

dp[i][3]=dp[i-1][2]//前一天卖出股票,不是保持状态

  1. dp数组如何初始化:dp[0][0]=-price[0] dp[0][1]=0 dp[0][2]=0 dp[0][3]=0

  1. 确定遍历顺序:顺序遍历 i=1 ==》max(dp[pricesize()-1][1,2,3])

  1. 举例推导dp数组

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

买卖股票的最佳实际含手续费(买卖多次,每次有手续费):力扣714

  1. 确定dp数组(dp table)以及下标的含义:dp[i][0]持有股最大现金;dp[i][1]不持有股票的最大先进

  1. 递推公式:

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

dp[i][1]=max(dp[i-1][1],dp[i-1][0]+price[i]-fee)

  1. dp数组如何初始化:dp[0][0]=-price[0] dp[0][1]=0

  1. 确定遍历顺序:顺序遍历 i=1 ==》max(dp[pricesize()-1][1,2,3])

  1. 举例推导dp数组

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];dp[0][1]=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 dp[n-1][1];}
};

子序列问题

子序列(不连续)

最长上升子序列:力扣300

最长公共子序列:力扣1143

不相交的线:力扣1035

子序列(连续)

最长连续递增子序列:力扣674

最长重复子数组:力扣718

最大子序和:力扣53

编辑距离

判断子序列:力扣392

不同的子序列:力扣115

两个字符串的删除操作:力扣583

编辑距离:力扣72

回文

回文子串:力扣647

最长回文子序列:力扣516

【代码随想录】Day38~Day46动态规划相关推荐

  1. 代码随想录训练营day46

    题目一:单词拆分 力扣题目链接 题目描述:给你一个字符串 s 和一个字符串列表 wordDict 作为字典.请你判断是否可以利用字典中出现的单词拼接出 s . 思路分析:代码随想录 单词就是物品,字符 ...

  2. 代码随想录44——动态规划:完全背包理论基础、518零钱兑换II、377组合总和IV

    文章目录 1.完全背包理论基础 2.518零钱兑换II 2.1.题目 2.2.解答 3.377组合总和IV 3.1.题目 3.2.解答 4.组合和排列问题的便利顺序 4.1.组合问题 4.2.排列问题 ...

  3. 【代码随想录】-动态规划专题

    文章目录 理论基础 斐波拉契数列 爬楼梯 使用最小花费爬楼梯 不同路径 不同路径 II 整数拆分 不同的二叉搜索树 背包问题--理论基础 01背包 二维dp数组01背包 一维数组(滚动数组) 装满背包 ...

  4. 【代码随想录】二刷-动态规划

    动态规划 代码随想录 解题步骤: 确定dp数组 确定递推公式--递推公式决定dp数组要如何初始化 dp数组如何初始化 确定遍历顺序 举例推导dp数组 509. 斐波那契数 class Solution ...

  5. _42LeetCode代码随想录算法训练营第四十二天-动态规划 | 121.买卖股票的最佳时机、122.买卖股票的最佳时机II

    _42LeetCode代码随想录算法训练营第四十二天-动态规划 | 121.买卖股票的最佳时机.122.买卖股票的最佳时机II 题目列表 121.买卖股票的最佳时机 122.买卖股票的最佳时机II 1 ...

  6. 代码随想录——动态规划

    一.动态规划的解题步骤 确定dp数组以及下标的含义 确定递推公式 dp数组如何初始化 确定遍历顺序 举例推导dp数组 二.基础动态规划问题 1.斐波那契数 class Solution { publi ...

  7. 代码随想录42——动态规划:0-1背包理论基础、0-1背包滚动数组、416分割等和子集

    文章目录 1.0-1背包理论基础 1.1.概述 1.2.0-1背包 1.3.二维dp数组01背包--动规五部曲 1.4.完整测试代码 2.0-1背包滚动数组 2.1.一维滚动数组 2.2.一维dp数组 ...

  8. 代码随想录训练营day38

    题目一:斐波那契数 力扣题目链接 题目描述:斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 .该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和. 给定 n ,请计算 ...

  9. 代码随想录算法训练营Day56动态规划:583.两个字符串的删除操作,72.编辑距离

    583.两个字符串的删除操作 文章链接:代码随想录 (programmercarl.com) 思路:动规五步曲 (1)确定dp数组及其含义 dp[i][j]表示字符串1在区间[0, i - 1]和字符 ...

最新文章

  1. C# 对应 Oracle 存储过程 的 SYS_REFCURSOR 应该 传入什么类型的参数?
  2. 自动驾驶的实现之路——几大关键传感器应用解析
  3. PostgreSQL中的数据库实例、模式、用户(角色)、表空间
  4. exception java doc,Javadoc和RuntimeException
  5. 单片机小白学步系列(二十三) IO口原理知识补充:双向IO口、互补推挽、高阻态
  6. U94222-循环往复【tarjan,DAGdp】
  7. mysql 事件 day hour_Mysql事件调度器(Event Scheduler)
  8. java 如何导出json文件_java导出json格式文件的示例代码
  9. 37岁程序员被裁员,面试华为阿里被拒,无奈降薪去小公司,结局出乎意料!
  10. TIOBE 1 月编程语言排行榜:C 语言再度「C 位」出道!
  11. 汇编语言和C语言单片机哪个更好用?
  12. OC Gen X Mac(一键制作黑苹果OpenCore EFI文件)v最新版
  13. 0603封装 1%贴片电阻代码表示的阻值
  14. 红包活动竟藏着这么多玩法(附使用技巧)
  15. H3C华三路由器nat避免生成null 0路由并解决nat需求
  16. 吱呦app-想法-交友软件
  17. 微软官方外挂,让你的win10更加好用
  18. PSMN4R8-100BSE MOSFET管 N-CH 100V D2PAK
  19. 东芝四轴机器人加相机
  20. Leetcode-数据结构-217. 存在重复元素

热门文章

  1. 【学习笔记】Spring-IOC-DI-AOP 学习笔记
  2. 手机品牌,型号,分辨率,系统,版本号
  3. texstudio深色主题设置
  4. H5项目适配系统深色模式方案
  5. 新颖的自我介绍_非常有创意的自我介绍
  6. 前端如何正确的添加、销毁全局事件监听
  7. centos7搭建DNS服务器全过程及注意事项(坑)
  8. PLSQL手动输入一个字符串,倒序打印输出字符串 , 如输入的是 abcd 打印输出 dcba
  9. 计算机网络系统工程技术要求,《SZDBZ 5.4-2008 信息系统工程建设技术规范 第4部分 计算机网络系统工程》.pdf...
  10. Vue中路由嵌套(子路由)