关键词:

           重叠子问题;每一个状态一定是由上一个状态推导出来(类似数列a^n = f(a^n-1,a^n-2));

           定义一个数组存放每个状态的数值,数组可能是一维也可能是二维;

           数组需要先初始化,就像数学里数列,你想推导a^n必须先知道a^n-1和a^n-2;

步骤:        

确定dp数组(dp table)以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组

题目: 

509. 斐波那契数

动规五部曲:

这里我们要用一个一维dp数组来保存递归的结果

确定dp数组以及下标的含义

dp[i]的定义为:第i个数的斐波那契数值是dp[i]

确定递推公式

为什么这是一道非常简单的入门题目呢?

因为题目已经把递推公式直接给我们了:状态转移方程 dp[i] = dp[i - 1] + dp[i - 2];

dp数组如何初始化

题目中把如何初始化也直接给我们了,如下:

dp[0] = 0;

dp[1] = 1;

确定遍历顺序

从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的

举例推导dp数组

按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为10的时候,dp数组应该是如下的数列:

0 1 1 2 3 5 8 13 21 34 55

class Solution {

public:

int fib(int N) {

if (N <= 1) return N;

vector dp(N + 1);

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. 爬楼梯

确定dp数组以及下标的含义

dp[i]: 爬到第i层楼梯,有dp[i]种方法

确定递推公式

从dp[i]的定义可以看出,dp[i] 可以有两个方向推出来。

首先是dp[i - 1],上i-1层楼梯,有dp[i - 1]种方法,那么再一步跳一个台阶不就是dp[i]了么。

还有就是dp[i - 2],上i-2层楼梯,有dp[i - 2]种方法,那么再一步跳两个台阶不就是dp[i]了么。

那么dp[i]就是 dp[i - 1]与dp[i - 2]之和!

所以dp[i] = dp[i - 1] + dp[i - 2] 。

dp数组如何初始化

dp[1] = 1,dp[2] = 2,这个初始化大家应该都没有争议的。

所以我的原则是:不考虑dp[0]如何初始化,只初始化dp[1] = 1,dp[2] = 2,然后从i = 3开始递推,这样才符合dp[i]的定义。

确定遍历顺序

从递推公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,遍历顺序一定是从前向后遍历的

举例推导dp数组

举例当n为5的时候,dp table(dp数组)应该是这样的

// 版本一

class Solution {

public:

int climbStairs(int n) {

if (n <= 1) return n; // 因为下面直接对dp[2]操作了,防止空指针

vector dp(n + 1);

dp[1] = 1;

dp[2] = 2;

for (int i = 3; i <= n; i++) { // 注意i是从3开始的

dp[i] = dp[i - 1] + dp[i - 2];

}

return dp[n];

}

};

746. 使用最小花费爬楼梯

确定dp数组以及下标的含义

使用动态规划,就要有一个数组来记录状态,本题只需要一个一维数组dp[i]就可以了。

dp[i]的定义:到达第i台阶所花费的最少体力为dp[i]。

确定递推公式

可以有两个途径得到dp[i],一个是dp[i-1] 一个是dp[i-2]。

dp[i - 1] 跳到 dp[i] 需要花费 dp[i - 1] + cost[i - 1]。

dp[i - 2] 跳到 dp[i] 需要花费 dp[i - 2] + cost[i - 2]。

那么究竟是选从dp[i - 1]跳还是从dp[i - 2]跳呢?

一定是选最小的,所以dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);

dp数组如何初始化

所以初始化 dp[0] = 0,dp[1] = 0;

确定遍历顺序

因为是模拟台阶,而且dp[i]由dp[i-1]dp[i-2]推出,所以是从前到后遍历cost数组就可以了。

举例推导dp数组

拿示例2:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] ,来模拟一下dp数组的状态变化,如下:

class Solution {

public:

int minCostClimbingStairs(vector& cost) {

vector 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. 不同路径

按照动规五部曲来分析:

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

dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

确定递推公式

想要求dp[i][j],只能有两个方向来推导出来,即dp[i - 1][j] 和 dp[i][j - 1]。

 dp[i - 1][j] 是从(0, 0)的位置到(i - 1, j)有几条路径,dp[i][j - 1]同理。

所以dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来。

dp数组的初始化

如何初始化呢,首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。

所以初始化代码为:

for (int i = 0; i < m; i++) dp[i][0] = 1;

for (int j = 0; j < n; j++) dp[0][j] = 1;

确定遍历顺序

这里要看一下递推公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1],dp[i][j]都是从其上方和左方推导而来,那么从左到右一层一层遍历就可以了。

这样就可以保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值的。

举例推导dp数组

如图所示:

class Solution {

public:

int uniquePaths(int m, int n) {

vector> dp(m, vector(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];

}

};

63. 不同路径 II

动规五部曲:

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

dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

确定递推公式

递推公式和62.不同路径一样,dp[i][j] = dp[i - 1][j] + dp[i][j - 1]。

但这里需要注意一点,因为有了障碍,(i, j)如果就是障碍的话应该就保持初始状态(初始状态为0)。

所以代码为:

if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i][j]

dp[i][j] = dp[i - 1][j] + dp[i][j - 1];

}

dp数组如何初始化

因为从(0, 0)的位置到(i, 0)的路径只有一条,所以dp[i][0]一定为1,dp[0][j]也同理。

但如果(i, 0) 这条边有了障碍之后,障碍之后(包括障碍)都是走不到的位置了,所以障碍之后的dp[i][0]应该还是初始值0。

vector> dp(m, vector(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;

确定遍历顺序

从递归公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 中可以看出,一定是从左到右一层一层遍历,这样保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值。

class Solution {

public:

int uniquePathsWithObstacles(vector>& obstacleGrid) {

int m = obstacleGrid.size();

int n = obstacleGrid[0].size();

if (obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1) //如果在起点或终点出现了障碍,直接返回0

return 0;

vector> dp(m, vector(n, 0));

//第一行和第一列初始化1时,只初始化到障碍出,例如第一行障碍是[0,0,0,1,0,0],初始化dp时为[1,1,1,0,0,0],后三个还是0,因为有障碍,到达的路径是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;

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. 整数拆分

重点是找出递推式 ,我看不出来。

动规五部曲,分析如下:

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

dp[i]:分拆数字i,可以得到的最大乘积为dp[i]。即dp[0]是0拆分的最大和,dp[1]是1拆分的最大和

确定递推公式

直接给公式:

递推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j});

一个是j * (i - j) 直接相乘。即只把i拆解成两数相加。

一个是j * dp[i - j],把i拆分成3数或者更多的数相加,即与前面状态相关。

dp[i]用于存放for循环遍历中的最大值。

dp的初始化

dp[0] dp[1] 忽略初始化,也就是没有意义的数值。

只初始化dp[2] = 1,从dp[i]的定义来说,拆分数字2,得到的最大乘积是1

确定遍历顺序

确定遍历顺序,先来看看递归公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

dp[i] 是依靠 dp[i - j]的状态,所以遍历i一定是从前向后遍历,先有dp[i - j]再有dp[i]。

所以遍历顺序为:

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

}

}

举例推导dp数组

举例当n为10 的时候,dp数组里的数值,如下:

class Solution {

public:

int integerBreak(int n) {

vector dp(n + 1);

dp[2] = 1;

//0,1无意义,2初始化为1,所以i从3开始

for (int i = 3; i <= n ; i++) {

//j=1,开始遍历比较相加和的乘积最大值,并把最大的放再dp[i]中

for (int j = 1; j <= i / 2; j++) {

//比较历史最大值dp[i];拆分两数和乘积;拆分3个及以上的乘积;

dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

}

}

return dp[n];

}

};

背包问题

01 背包

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

背包最大重量为4。

物品为:

重量价值物品0115物品1320物品2430

问背包能背的物品最大价值是多少?

 解析

动规五部曲:

确定dp数组以及下标的含义

对于背包问题,有一种写法, 是使用二维数组,即dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。

横坐标i:物品是否可以选取,例如i=2表示物品0,1,2都可以选取

列坐标:背包重量

dp[i][j]:表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。

确定递推公式

再回顾一下dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。

那么可以有两个方向推出来dp[i][j],

不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。)放物品i:dp[i - 1][j - weight[i]] + value[i]                                                                                                      value[i] :i的价值;                                                                                                                    dp[i - 1][j - weight[i]] :除去i的重量后,剩余背包容量所能得到的最大价值,这个价                                                      值可能是由多个物品加起来的)

所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

dp数组如何初始化

背包为0,什么都放不下,价值为0

物品只能选0,无论背包多大价值都只能是15

剩下dp[1-2][1-4]的因为都会被覆盖,所以初始化为0就行了

for (int j = 0 ; j < weight[0]; j++) { // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略,但很多同学应该没有想清楚这一点。

dp[0][j] = 0;

}

// 正序遍历

for (int j = weight[0]; j <= bagweight; j++) {

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

}

}

举例推导dp数组

来看一下对应的dp数组的数值,如图:

void test_2_wei_bag_problem1() {

vector weight = {1, 3, 4};

vector value = {15, 20, 30};

int bagweight = 4;

// 二维数组

vector> dp(weight.size(), vector(bagweight + 1, 0));

// 初始化

for (int j = weight[0]; j <= bagweight; j++) {

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() {

test_2_wei_bag_problem1();

}

01背包(滚动数组)

 问题:

背包最大重量为4。

物品为:

重量价值物品0115物品1320物品2430

问背包能背的物品最大价值是多少?

一维dp数组(滚动数组)

关键思想:

        二维数组时计算i层用到了i-1层的结果,而当前i层并不影响计算。所以我们可以使用一个一维数组就可以进行运算。计算i层时覆盖i-1层数据。

        而覆盖的时候会存在一个问题:递推公式中需要计算 dp[i - 1][j - weight[i]],即计算dp[i][j]时,需要使用到上一层的0-j列的数据。如果我们从左往右去覆盖刷新数组,会导致我们计算后面的dp[i - 1][j - weight[i]]时用了已经刷新过的列数据,所以我们应该从后往前去刷新数据。

 详细解释:

对于背包问题其实状态都是可以压缩的。

在使用二维数组的时候,递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

其实可以发现如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);

与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j](一维数组,也可以理解是一个滚动数组)。

这就是滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层。

读到这里估计大家都忘了 dp[i][j]里的i和j表达的是什么了,i是物品,j是背包容量。

dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。

一定要时刻记住这里i和j的含义,要不然很容易看懵了。

动规五部曲分析如下:

确定dp数组的定义

在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。

一维dp数组的递推公式

dp[j]为 容量为j的背包所背的最大价值,那么如何推导dp[j]呢?

dp[j]可以通过dp[j - weight[i]]推导出来,dp[j - weight[i]]表示容量为j - weight[i]的背包所背的最大价值。

dp[j - weight[i]] + value[i] 表示 容量为 j - 物品i重量 的背包 加上 物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j])

此时dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,毕竟是求最大价值,

所以递归公式为:

dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

可以看出相对于二维dp数组的写法,就是把dp[i][j]中i的维度去掉了。

一维dp数组如何初始化

关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱。

dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就应该是0,因为背包容量为0所背的物品的最大价值就是0。

那么dp数组除了下标0的位置,初始为0,其他下标应该初始化多少呢?

看一下递归公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

dp数组在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。

这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了。

那么我假设物品价值都是大于0的,所以dp数组初始化的时候,都初始为0就可以了。

一维dp数组遍历顺序

代码如下:

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

}

}

这里大家发现和二维dp的写法中,遍历背包的顺序是不一样的!

二维dp遍历的时候,背包容量是从小到大,而一维dp遍历的时候,背包是从大到小。

为什么呢?

倒序遍历是为了保证物品i只被放入一次!。但如果一旦正序遍历了,那么物品0就会被重复加入多次!

举一个例子:物品0的重量weight[0] = 1,价值value[0] = 15

如果正序遍历

dp[1] = dp[1 - weight[0]] + value[0] = 15

dp[2] = dp[2 - weight[0]] + value[0] = 30

此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历。

为什么倒序遍历,就可以保证物品只放入一次呢?

倒序就是先算dp[2]

dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0)

dp[1] = dp[1 - weight[0]] + value[0] = 15

所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了。

那么问题又来了,为什么二维dp数组遍历的时候不用倒序呢?

因为对于二维dp,dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖!

(如何这里读不懂,大家就要动手试一试了,空想还是不靠谱的,实践出真知!)

再来看看两个嵌套for循环的顺序,代码中是先遍历物品嵌套遍历背包容量,那可不可以先遍历背包容量嵌套遍历物品呢?

不可以!

因为一维dp的写法,背包容量一定是要倒序遍历(原因上面已经讲了),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。

倒序遍历的原因是,本质上还是一个对二维数组的遍历,并且右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖。

(这里如果读不懂,就再回想一下dp[j]的定义,或者就把两个for循环顺序颠倒一下试试!)

所以一维dp数组的背包在遍历顺序上和二维其实是有很大差异的!,这一点大家一定要注意。

举例推导dp数组

一维dp,分别用物品0,物品1,物品2 来遍历背包,最终得到结果如下:

void test_1_wei_bag_problem() {

vector weight = {1, 3, 4};

vector value = {15, 20, 30};

int bagWeight = 4;

// 初始化

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

}

416. 分割等和子集

 建模成01背包问题(01背包:物品只能取一次)

关键思想:

1.问题描述成:如果能找出一个子集,他的和等于集合总和的一半(sum/2),则表示目标能够达到。

2.背包的最大容积为问题描述中的sum/2。此时问题变成是否找得到子集使得dp[sum/2] ==sum/2

3.上图中下标i表示当前背包容积

4.集合中的数即时重量也是价值 

5.dp[i]表示背包容积为i时,集合中所能找到最大价值

动规五部曲:

确定dp数组以及下标的含义

01背包中,dp[j] 表示: 容量为j的背包,所背的物品价值最大可以为dp[j]。

本题中每一个元素的数值既是重量,也是价值。

套到本题,dp[j]表示 背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]。

那么如果背包容量为target, dp[target]就是装满 背包之后的重量,所以 当 dp[target] == target 的时候,背包就装满了。

确定递推公式

01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

本题,相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。

所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);

dp数组如何初始化

在01背包,一维dp如何初始化,已经讲过,

从dp[j]的定义来看,首先dp[0]一定是0。

如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。

这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了。

本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了。

代码如下:

// 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200

// 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了

vector dp(10001, 0);

确定遍历顺序

如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!

代码如下:

// 开始 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]);

}

}

举例推导dp数组

dp[j]的数值一定是小于等于j的。

如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j,理解这一点很重要。

用例1,输入[1,5,11,5] 为例,如图:

class Solution {

public:

bool canPartition(vector& nums) {

int sum = 0;

// dp[i]中的i表示背包内总和

// 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200

// 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了

vector dp(10001, 0);

for (int i = 0; i < nums.size(); i++) {

sum += nums[i];

}

// 也可以使用库函数一步求和

// int sum = accumulate(nums.begin(), nums.end(), 0);

if (sum % 2 == 1) return false;

int target = sum / 2;

// 开始 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]);

}

}

// 集合中的元素正好可以凑成总和target

if (dp[target] == target) return true;

return false;

}

};

完全背包

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

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。

关键点 :        

        代码上完全背包区别与01背包的地方 :滚动数组中j的遍历改为从左往右,这样一个物品就会被重复装入背包。

滚动数组部分讨论过,如果j是从左往右,则dp[j - weight[i]]计算的是本次遍历物品循环中已经更新过的数据。

//01背包的遍历

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 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[j] += dp[j - coins[i]];

初始化:dp[0]=1;//当总和为0是,只有一种方法即一个不取。

class Solution {

public:

int change(int amount, vector& coins) {

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

}

};

推荐链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。