01背包

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

二维数组解法

动规五部曲 :

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

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

  2. 确定递推公式

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

  3. dp数组如何初始化

    1
    2
    3
    4
    5
    6
    7
    for (int j = 0 ; j < weight[0]; j++) { 
    dp[0][j] = 0;
    }
    // 正序遍历
    for (int j = weight[0]; j <= bagweight; j++) {
    dp[0][j] = value[0];
    }
  4. 确定遍历顺序 (两种)

    上面是对于dp[i] [j]的一个图。可以看到,i是代表从下标为0~i的物品里随便取,j是背包重量。那么可以先遍历i或者先遍历j

    • 先物品再背包

      理解 : 这就是先遍历i 。具体可以解释为 :先给定物品的选择范围(0~i),在这个范围里面,看各个背包重量下j,能取到的价值最大值。

      递推就是 : 拿i个物品的最大值,需要用拿i-1个物品的最大值来递推。

      1
      2
      3
      4
      5
      6
      7
      8
      // 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]);

      }
      }
    • 先背包再物品

      理解 : 这就是先遍历j 。具体可以解释为 : 先给定背包的重量j ,求在这个背包重量下,如何在所有物品中找到一个组合,能够得到价值最大值。

      递推也是 : 拿i个物品的最大值,需要用拿i-1个物品的最大值来递推。

      1
      2
      3
      4
      5
      6
      7
      // weight数组的大小 就是物品个数
      for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
      for(int i = 1; i < weight.size(); i++) { // 遍历物品
      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]);
      }
      }
  5. 举例推导dp数组

  6. 完整代码

    动态规划的核心思想避免重复计算在01背包问题中体现得淋漓尽致。

    第i件物品装入或者不装入而获得的最大价值完全可以由前面i-1件物品的最大价值决定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    public class BagProblem {
    public static void main(String[] args) {
    int[] weight = {1,3,4};
    int[] value = {15,20,30};
    int bagSize = 4;
    testWeightBagProblem(weight,value,bagSize);
    }

    /**
    * 动态规划获得结果
    * @param weight 物品的重量
    * @param value 物品的价值
    * @param bagSize 背包的容量
    */
    public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){

    // 创建dp数组
    int goods = weight.length; // 获取物品的数量
    int[][] dp = new int[goods][bagSize + 1];

    // 初始化dp数组
    // 创建数组后,其中默认的值就是0
    for (int j = weight[0]; j <= bagSize; j++) {
    dp[0][j] = value[0];
    }

    // 填充dp数组,这里是先物品再背包
    for (int i = 1; i < weight.length; i++) {
    for (int j = 1; j <= bagSize; j++) {
    if (j < weight[i]) {
    /**
    * 当前背包的容量都没有当前物品i大的时候,是不放物品i的
    * 那么前i-1个物品能放下的最大价值就是当前情况的最大价值
    */
    dp[i][j] = dp[i-1][j];
    } else {
    /**
    * 当前背包的容量可以放下物品i
    * 那么此时分两种情况:
    * 1、不放物品i
    * 2、放物品i
    * 比较这两种情况下,哪种背包中物品的最大价值最大
    */
    dp[i][j] = Math.max(dp[i-1][j] , dp[i-1][j-weight[i]] + value[i]); //也就是递推公式
    }
    }
    }

    // 打印dp数组
    for (int i = 0; i < goods; i++) {
    for (int j = 0; j <= bagSize; j++) {
    System.out.print(dp[i][j] + "\t");
    }
    System.out.println("\n");
    }
    }
    }


一维数组解法

首先来回味一下二维数组的递推公式 :

dp[i][j] = Math.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]上,不如只用一个一维数组了(i没有存在的必要了) :

dp[j]

这个dp[j]就是一个滚动数组(需要满足的条件是上一层可以重复利用,直接拷贝到当前层。)

如此一来 ,动规五部曲可以写成 :

  1. 确定dp数组含义

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

  2. dp数组的递推公式

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

    解释:

    dp[j]可以通过dp[j - weight[i]]推导出来(这一点参考上面的二维数组dp[i] [j] = max(dp[i - 1] [j], dp[i - 1] [j - weight[i]] + value[i]);)

    dp[j - weight[i]] : 容量为j - weight[i]的背包所背的最大价值。

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

    公式中的 dp[j] : 二维dp数组中的dp[i-1][j],即不放物品i

    公式中的dp[j - weight[i]] + value[i] : 即放物品i

  3. dp数组的初始化

    dp数组初始化的时候,都初始为0就可以了。

  4. dp数组遍历

    1
    2
    3
    4
    5
    6
    7
    8
    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[i][j]的值只与dp[i-1][0,...,j-1]有关,所以我们可以采用动态规划常用的方法(滚动数组)对空间进行优化(即去掉dp的第一维)。

    需要注意的是,为了防止上一层循环的dp[0,...,j-1]被覆盖,循环的时候 j 只能逆向枚举(空间优化前没有这个限制)

  5. 代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public static void main(String[] args) {
    int[] weight = {1, 3, 4};
    int[] value = {15, 20, 30};
    int bagWight = 4;
    testWeightBagProblem(weight, value, bagWight);
    }

    public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
    int wLen = weight.length;
    //定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
    int[] dp = new int[bagWeight + 1];
    //遍历顺序:先遍历物品,再遍历背包容量
    for (int i = 0; i < wLen; i++){
    for (int j = bagWeight; j >= weight[i]; j--){
    dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
    }
    }
    //打印dp数组
    for (int j = 0; j <= bagWeight; j++){
    System.out.print(dp[j] + " ");
    }
    }

完全背包

直接滚动数组来写。

与01背包的不同点在于,对于物品的拿出没有限制。

背包小总结

递推公式

背包的最大值

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

达到背包最大值的方法数(排列还是组合在遍历时有区别):

dp[j] += dp[j - nums[i]]

达到背包最大值的最小物品数量

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

遍历顺序

01背包求组合/最大值

外层for循环遍历物品,内层for遍历背包(物品从0下标开始,背包从bagWeight开始,– 至weight[i])

完全背包求最大值

外层for循环遍历物品,内层for遍历背包(物品从0下标开始,背包从nums[i]下标开始 , ++ 至 bagWeight)

完全背包求方法数

组合数 : 外层for循环遍历物品,内层for遍历背包。(物品从0下标开始,背包从nums[i]下标开始)

排列数 : 外层for循环遍历背包,内层for循环遍历物品。(背包和物品都从0下标开始)

416.分割等和子集(01求max)

一维写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
分析 : 分割成两个和相等的子集 ,那么两个子集的和都为数组的和/2
如何套用背包 ? 因为每个元素只能用一次,所以是01背包
1.确定背包体积 : 两个 ,都是sum/2
2.待选商品的价值和重量 : 都为数组中数字的值
3.背包如果装满,就找到了值
明确了此问题之后,采用动规
1.确定dp数组的含义
dp[j] : 背包总容量(所能装的总重量)是j,即放进物品后,背的最大重量为dp[j]。
背包容量为target, dp[target]就是装满 背包之后的重量,所以 当 dp[target] == target 的时候,背包就装满了。
2.dp的递推公式
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
3.初始化
dp[j]的定义来看,首先dp[0]一定是0。
本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了。
(如果题目给的价值有负数,那么非0下标就要初始化为负无穷。)
4.遍历顺序
如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!

*/
class Solution {
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0){
return false;
}
int n = nums.length;
int sum = 0;
for(int num : nums){
sum += num;
}
// 奇数肯定不行
if(sum % 2 != 0){
return false;
}
// 目标数,也是背包的体积
int target = sum / 2;
// 为什么是target+1位的长度? 因为target是背包最大容量,也就是j
int[] dp = new int[target + 1];
for(int i = 0; i < n; i ++){
// 内层for循环倒序遍历
for(int j = target; j >= nums[i]; j -- ){
// 物品的价值和重量都是nums[i]
dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}

二维写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
分析 : 分割成两个和相等的子集 ,那么两个子集的和都为数组的和/2
如何套用背包 ? 因为每个元素只能用一次,所以是01背包
1.确定背包体积 : 两个 ,都是sum/2
2.待选商品的价值和重量 : 都为数组中数字的值
3.背包如果装满,就找到了值
明确了此问题之后,采用动规
1.确定dp数组的含义
dp[i][j] : 从下标为0-i的数字里选一些, 背包容量为target, 他们的值加起来恰好为target
2.dp的递推公式
dp[i][j] = max(dp[i - 1][j], dp[i - i][j - weight[i]] + value[i]);
3.初始化
dp[0][j] :物品没得选,看weight[i]有没有超过target,所以值要么0要么value[i]
4.遍历顺序
// 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]);
}
}

*/
class Solution {
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0){
return false;
}
int n = nums.length;
int sum = 0;
for(int num : nums){
sum += num;
}
// 奇数肯定不行
if(sum % 2 != 0){
return false;
}
// 目标数,也是背包的体积
int target = sum / 2;
// 创建二维状态数组,行:物品索引(有n个),列:容量(包括 0,最大target,所以长度为target + 1)
/*
dp[i][j]表示从数组的 [0, i] 这个子区间内挑选一些正整数
每个数只能用一次,使得这些数的和恰好等于 j。
*/
int[][] dp = new int[n][target + 1];

// 初始化dp[0][j],是遍历j。看j和第0个数字谁大,j大就可以初始化为第0个数,j小就只能初始化为0(放不下)
for(int j = 0;j <= target; j ++){
dp[0][j] = j > nums[0] ? nums[0] : 0;
}
for(int i = 1; i < n; i++) { // 遍历物品
for(int j = 0; j <= target; j++) { // 遍历背包容量
if (j < nums[i]){
dp[i][j] = dp[i - 1][j];
}
else{
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]);
}
}
}
return dp[n - 1][target] == target;
}
}

1049.最后一块石头的重量Ⅱ(01求max)

分成两堆转化成背包问题,要思考清楚最后到底剩多少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
如何求解?尽量让石头分为重量相似的两堆,粉碎后的值就是这两堆的差值
那么假设总重量为totalWeight,那么分成两堆,尽可能接近totalWeight/2
转化成背包问题 : 有一个bagWeight = totalWeight/2的背包,在stones[]数组中取,尽可能把包装满

*/
class Solution {
public int lastStoneWeightII(int[] stones) {
// 物品总重量
int totalWeight = 0;
// 背包最大重量
int bagWeight = 0;
// 背包长度
int len = stones.length;
// 最后的差值
int res = 0;
for(int stone : stones){
totalWeight += stone;
}
bagWeight = (int)Math.floor(totalWeight / 2);

// 然后就开始背包问题的求解
// dp数组,代表的意思是背包能装进的最大值
int[][] dp = new int[len][bagWeight + 1];
// dp数组的初始化
for(int j = 0;j < bagWeight + 1;j ++){
dp[0][j] = j >= stones[0] ? stones[0] : 0;
}
// 遍历 : 先物品,再背包
for(int i = 1;i < len;i ++){
for(int j = 1;j < bagWeight + 1;j ++){

if(j < stones[i]){
dp[i][j] = dp[i - 1][j];
}else{
dp[i][j] = Math.max(dp[i - 1][j] ,dp[i -1 ][j - stones[i]] + stones[i]);
}
}
}

res = totalWeight - dp[len - 1][bagWeight] - dp[len - 1][bagWeight];
System.out.println(totalWeight);
System.out.println(bagWeight);
System.out.println(dp[len - 1][bagWeight]);
return res;
}
}

494.目标和(01求组合)

这里不是求能不能装满背包,
而是求能装满背包的方法。
另外要充分考虑特殊情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
思考 : 一组数字,经过+ - 操作后,得到target
那么可以分为两组,一组num1[]是被加的数,一组num2[]是被减的数
然后目标是 num1的和 - num2的和 = target ,(且num1的和 + num2的和 = nums的和)
得到两个公式 : num1 + num2 = total ; num1 - num2 = target
相加得到 : num1 = (total + target)/2
转换成背包问题 :有一个背包重量为(total + target)/2,在nums里面取,看能不能装满
但是这只能看有没有解,怎么看解的个数 ?
动规五部曲:
1.dp定义
用单层数组来看的话 dp[j] 表示:填满j这么大容积的包,有dp[j]种方法
2.dp递推公式
**也就是把 所有的 dp[j - nums[i]] 累加起来。**
求组合类问题的公式,都是类似这种:dp[j] += dp[j - nums[i]]
解释 : 对于从n个里面跳实体,求装满j的容量的方法数量,只需要把物品挨个拿出物品i(i∈0~n-1),看在n - 1个物品里满足装满dp[j - nums[i]]的个数,再累加即可
3.dp初始化
dp[0] = 1;
从递推公式可以看出,在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递推结果将都是0。
*/
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int totalWeight = 0;
int bagWeight = 0;
int len = nums.length;
// 长度为1
if(len == 1){
return (Math.abs(nums[0]) == Math.abs(target)) ? 1 : 0;
}
for(int num : nums) {
totalWeight += num;
}

if((totalWeight + target) % 2 != 0 || Math.abs(totalWeight) < Math.abs(target)){
return 0;
}else{
bagWeight = (totalWeight + target) / 2;
}
// 负数target
if(bagWeight < 0){
bagWeight = -bagWeight;
}
int[] dp = new int[bagWeight + 1];
dp[0] = 1;
for(int i = 0;i < len; i++){
for(int j = bagWeight;j >= nums[i]; j --){
dp[j] += dp[j - nums[i]];
}
}

return dp[bagWeight];
}
}

474.一和零(01求组合、二维物品)

物品是二维的,。思想是一维(滚动数组)的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
难得一 做不了一点

题目要求 : 给定strs[] 求的是最多可以有几个strs[]里的元素可以满足题目要求

转化成背包 :
dp[i][j] : 代表最多有i个0和j个1的子集最大数量 ,
注意 : 虽然这是二维数组,但是因为石头是二维的,所以这是一维的思想
如果要用二维动态规划写,可能要写成三维数组。

递推公式 :
本题 : dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
(供参考)普通 : dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
解释 :
每个str里一共有zeroNum个0,oneNum个1。
dp[i][j] 最多有i个0和j个1的子集最大数量
dp[i][j] 可以由前一个strs里的字符串推导出来(前一个指的是字符串的长度-1下条件下的字符串,参考一维背包的“前一个”)
dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1)的意思就是
要么不考虑新增的这个str的最大长度,使用原来的dp[i][j]
要么考虑,使用了新增的,那么0和1的个数少了,那么个数也就多了1
然后看他们两谁大。

遍历 : 请参考一维背包数组的遍历 : 从后往前
*/

class Solution {
public int findMaxForm(String[] strs, int m, int n) {
//dp[i][j]表示i个0和j个1时的最大子集
int[][] dp = new int[m + 1][n + 1];
// oneNum和zeroNum代表每个strs[]里面 0和1 的数量
int oneNum, zeroNum;
for (String str : strs) {
oneNum = 0;
zeroNum = 0;
for (char ch : str.toCharArray()) {
if (ch == '0') {
zeroNum++;
} else {
oneNum++;
}
}
//倒序遍历
for (int i = m; i >= zeroNum; i--) {
for (int j = n; j >= oneNum; j--) {
dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
}
}
}
return dp[m][n];
}
}

518.零钱兑换Ⅱ(完全求组合)

完全背包的基本应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
此题与494.目标和相似,只不过494为01背包,此题为完全背包

确定dp数组含义 :
dp[j]代表凑到amount有多少种方法

确定递推公式 :
dp[j] += dp[j - nums[i]]
求组合类问题的公式,都是类似这种:dp[j] += dp[j - nums[i]]
解释 : 对于从n个里面跳实体,求装满j的容量的方法数量,只需要把物品挨个拿出物品i(i∈0~n-1),看在n - 1个物品里满足装满dp[j - nums[i]]的个数,再累加即可

dp遍历顺序
对于完全背包 :len个物品,bagWeight的背包重量 :
for(int i = 0;i < len + 1 ; i++){
for(int j = nums[i];j < bagWeight +1 ; j ++){
dp[j] += dp[j - nums[i]]
}
}

dp初始化:
dp[0] = 1 (一切从0开始)
*/

class Solution {
public int change(int amount, int[] coins) {
int len = coins.length;
int[] dp = new int[amount + 1];
dp[0] = 1;
// 要从i = 0开始验证
for(int i = 0; i < len; i++){
for(int j = coins[i]; j < amount + 1 ; j ++){
if(j < coins[i]){
dp[j] = dp[j];
}else{
dp[j] += dp[j - coins[i]];
}

}
}
return dp[amount];
}
}

377.组合总和Ⅳ(完全求排列)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
这题和518.零钱兑换的区别在于,518没有顺序,这题有顺序
如果判断顺序?
dp数组的含义
dp[i] 拿到j的有序数组的数量
递推公式
虽然本题是有序的,但是用dp[i] += dp[i - nums[j]] 同样能求出所有情况
解释 : 看遍历顺序。
遍历顺序
本题不止需要看组合数,还需要看排列数。
就是说要考虑元素之间的顺序。
记住 : (虽然我也不知道为啥)
如果求组合数就是外层for循环遍历物品,内层for遍历背包。(物品从0下标开始,背包从nums[i]下标开始)
如果求排列数就是外层for遍历背包,内层for循环遍历物品。(背包和物品都从0下标开始)

*/
class Solution {
public int combinationSum4(int[] nums, int target) {
int len = nums.length;
int[] dp = new int[target + 1];
dp[0] = 1;
// 注意这里的遍历顺序 :先背包 ,再物品
for (int i = 0; i <= target; i++) {
for (int j = 0; j < nums.length; j++) {
if (i >= nums[j]) {
dp[i] += dp[i - nums[j]];
}
}
}
return dp[target];
}
}

322.零钱兑换(完全求最小物品数)

难点 : 在于初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
在518.零钱兑换Ⅱ中,dp[j]是含义是凑到j元,一共有多少种方法。
就是有个背包为容量j,有多少种方法装满。
那么递推公式是dp[j] += dp[j - nums[i]] (这是组合数的公式)
在这里,dp[j]是凑到j元,最少可以拿几个钱。
就是一个背包容量为j,最少多少个石头可以装满。
已知: dp[j-1] 最少多少石头可以装满
递推公式 : dp[i][j] = min{ dp[i-1][j],dp[i - 1][i - 1][j - nums[i]] + 1 }
换成滚动数组 : dp[j] = min(dp[j],dp[j - coins[i]] + 1);
递推公式: 对于完全背包的滚动数组,len个物品,bagWeight的背包重量:
for(int i = 0;i < len + 1 ; i++){
for(int j = nums[i];j < bagWeight +1 ; j ++){
dp[j] += dp[j - nums[i]]
}
}
初始化 :
dp[0] = 0
考虑到递推公式的特性,dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。
dp[i] = Integer.MAX_VALUE;
*/

class Solution {
public int coinChange(int[] coins, int amount) {
//物品长度
int len = coins.length;
// 背包
int[] dp = new int[amount + 1];
// 初始化
dp[0] = 0;
for(int i = 1;i < dp.length;i ++){
dp[i] = Integer.MAX_VALUE;
}
// 遍历
for(int i = 1;i * i < len ; i ++){
for(int j = coins[i];j < amount + 1; j ++){
//只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要
if (dp[j - coins[i]] != Integer.MAX_VALUE) {
//选择硬币数目最小的情况
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
}
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}
}

279.完全平方数(完全求最小物品数)

类似322.零钱兑换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
有点像322.零钱兑换,都是求最少数量,
所以dp含义和递推公式可以先确定 :
dp[j] : 对于一个含量为j的背包,在i个数字里面拿平方,有多少种方法可以让数字最少
dp[j] = min{dp[j] , dp[j-weight[i]]+1}
其中数字i应该小于等于Math.floor( Math.sqrt(j) )
别的应该都一样
*/
class Solution {
public int numSquares(int n) {
// 定义最大值
int max = Integer.MAX_VALUE ;
// dp数组
int[] dp = new int[n + 1];
// 初始化
dp[0] = 0;
for(int i = 1;i < n + 1; i ++){
dp[i] = max;
}
// 遍历
for(int i = 1; i * i < n + 1; i ++){
for (int j = i * i; j < n + 1; j++) {
if (dp[j - i * i] != max) {
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
}
}
}
return dp[n] ;
}
}

139.单词拆分(字符串的背包形式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
问题 : 给定一个字符串数组,看是否能拼接成指定字符串
转化成背包 : 有一个容量为s的背包,在wordDict 里面选物品,看是否能恰巧装满。(也就是字符串里面的所有字符有没有在数组中出现过)
难点 : 背包的容量 、物品的weight和value应该如何确定?(以前都是数字,现在是字符串)
确定dp的含义 :
dp[j]表示长度为j的字符串可以拆开
递推公式 :
dp[j] = dp[i] && s[i,j]存在
解释 : dp[j]要为true ,那么dp[i] (i < j)必须为真,而且i~j这一段的字符串也应该存在(可以用set.contains()来判断)
初始化 : dp[0] = ture
遍历顺序
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。

*/
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
// 把一个字符串列表转化成哈希表
HashSet<String> set = new HashSet<>(wordDict);
// 这里,背包是s,物品是List
// 一个长度为len + 1的bool背包
boolean[] valid = new boolean[s.length() + 1];
valid[0] = true;
//
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i && !valid[i]; j++) {
if (set.contains(s.substring(j, i)) && valid[j]) {
valid[i] = true;
}
}
}
return valid[s.length()];
}
}