代码随想录算法训练营第42天 | 01背包问题,你该了解这些! 01背包问题,你该了解这些! 滚动数组 416. 分割等和子集

目录

01背包问题,你该了解这些!

01 背包

二维dp数组01背包

💻实现代码

01背包问题,你该了解这些! 滚动数组

一维dp数组(滚动数组)

💻实现代码

416. 分割等和子集

💡解题思路

01背包问题

💻实现代码


01背包问题,你该了解这些!

416.分割等和子集1

01 背包

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

动态规划-背包问题

在下面的讲解中,我举一个例子:

背包最大重量为4。

物品为:

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

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

以下讲解和图示中出现的数字都是以这个例子为例。

二维dp数组01背包

依然动规五部曲分析一波。

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

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

只看这个二维数组的定义,大家一定会有点懵,看下面这个图:

动态规划-背包问题1

要时刻记着这个dp数组的含义,下面的一些步骤都围绕这dp数组的含义进行的,如果哪里看懵了,就来回顾一下i代表什么,j又代表什么。

  1. 确定递推公式

再回顾一下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]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值

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

  1. dp数组如何初始化

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

首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。如图:

动态规划-背包问题2

在看其他情况。

状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。

dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。

那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。

当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号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];
}

此时dp数组初始化情况如图所示:

动态规划-背包问题7

dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢?

其实从递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出dp[i][j] 是由左上方数值推导出来了,那么 其他下标初始为什么数值都可以,因为都会被覆盖。

初始-1,初始-2,初始100,都可以!

但只不过一开始就统一把dp数组统一初始为0,更方便一些。

如图:

动态规划-背包问题10

最后初始化代码如下:

// 初始化 dp
vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
for (int j = weight[0]; j <= bagweight; j++) {
    dp[0][j] = value[0];
}

费了这么大的功夫,才把如何初始化讲清楚,相信不少同学平时初始化dp数组是凭感觉来的,但有时候感觉是不靠谱的

  1. 确定遍历顺序

在如下图中,可以看出,有两个遍历的维度:物品与背包重量

动态规划-背包问题3

那么问题来了,先遍历 物品还是先遍历背包重量呢?

其实都可以!! 但是先遍历物品更好理解

那么我先给出先遍历物品,然后遍历背包重量的代码。

// 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数组)

例如这样:

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

为什么也是可以的呢?

要理解递归的本质和递推的方向

dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 递归公式中可以看出dp[i][j]是靠dp[i-1][j]和dp[i - 1][j - weight[i]]推导出来的。

dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括正上方向),那么先遍历物品,再遍历背包的过程如图所示:

动态规划-背包问题5

再来看看先遍历背包,再遍历物品呢,如图:

动态规划-背包问题6

大家可以看出,虽然两个for循环遍历的次序不同,但是dp[i][j]所需要的数据就是左上角,根本不影响dp[i][j]公式的推导!

但先遍历物品再遍历背包这个顺序更好理解。

其实背包问题里,两个for循环的先后循序是非常有讲究的,理解遍历顺序其实比理解推导公式难多了

  1. 举例推导dp数组

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

动态规划-背包问题4

最终结果就是dp[2][4]。

💻实现代码

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

import java.util.Arrays;

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

    /**
     * 初始化 dp 数组做了简化(给物品增加冗余维)。这样初始化dp数组,默认全为0即可。
     * dp[i][j] 表示从下标为[0 - i-1]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
     * 其实是模仿背包重量从 0 开始,背包容量 j 为 0 的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为 0。
     * 可选物品也可以从无开始,也就是没有物品可选,即dp[0][j],这样无论背包容量为多少,背包价值总和一定为 0。
     * @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 + 1][bagSize + 1];  // 给物品增加冗余维,i = 0 表示没有物品可选

        // 初始化dp数组,默认全为0即可
        // 填充dp数组
        for (int i = 1; i <= goods; i++) {
            for (int j = 1; j <= bagSize; j++) {
                if (j < weight[i - 1]) {  // i - 1 对应物品 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 - 1]] + value[i - 1]);  // i - 1 对应物品 i
                }
            }
        }

        // 打印dp数组
        for(int[] arr : dp){
            System.out.println(Arrays.toString(arr));
        }
    }
}

01背包问题,你该了解这些! 滚动数组

我们通过01背包,来彻底讲一讲滚动数组!

接下来还是用如下这个例子来进行讲解

背包最大重量为4。

物品为:

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

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

一维dp数组(滚动数组)

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

在使用二维数组的时候,递推公式: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的含义,要不然很容易看懵了。

动规五部曲分析如下:

  1. 确定dp数组的定义

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

  1. 一维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的维度去掉了。

  1. 一维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就可以了。

  1. 一维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数组的背包在遍历顺序上和二维其实是有很大差异的!,这一点大家一定要注意。

  1. 举例推导dp数组

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

动态规划-背包问题9

💻实现代码

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

416. 分割等和子集

题目链接:416. 分割等和子集

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200

示例 1:

  • 输入: [1, 5, 11, 5]
  • 输出: true
  • 解释: 数组可以分割成 [1, 5, 5] 和 [11].

示例 2:

  • 输入: [1, 2, 3, 5]
  • 输出: false
  • 解释: 数组不能分割成两个元素和相等的子集.

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

💡解题思路

01背包问题

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

背包问题有多种背包方式,常见的有:01背包、完全背包、多重背包、分组背包和混合背包等等。

要注意题目描述中商品是不是可以重复放入。

即一个商品如果可以重复多次放入是完全背包,而只能放入一次是01背包,写法还是不一样的。

要明确本题中我们要使用的是01背包,因为元素我们只能用一次。

回归主题:首先,本题要求集合里能否出现总和为 sum / 2 的子集。

那么来一一对应一下本题,看看背包问题如何来解决。

只有确定了如下四点,才能把01背包问题套到本题上来。

  • 背包的体积为sum / 2
  • 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
  • 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
  • 背包中每一个元素是不可重复放入。

以上分析完,我们就可以套用01背包,来解决这个问题了。

动规五部曲分析如下:

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

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

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

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

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

有录友可能想,那还有装不满的时候?

拿输入数组 [1, 5, 11, 5],举例, dp[7] 只能等于 6,因为 只能放进 1 和 5。

而dp[6] 就可以等于6了,放进1 和 5,那么dp[6] == 6,说明背包装满了。

  1. 确定递推公式

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

  1. dp数组如何初始化

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

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

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

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

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

代码如下:

// 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
// 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
vector<int> dp(10001, 0);
  1. 确定遍历顺序

在动态规划:关于01背包问题,你该了解这些!(滚动数组)

(opens new window)中就已经说明:如果使用一维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]);
    }
}
  1. 举例推导dp数组

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

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

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

416.分割等和子集2

最后dp[11] == 11,说明可以将这个数组分割成两个子集,使得两个子集的元素和相等。

💻实现代码

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;
        int[] dp = new int[target + 1];
        for(int i = 0; i < n; i++) {
            for(int j = target; j >= nums[i]; j--) {
                //物品 i 的重量是 nums[i],其价值也是 nums[i]
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }
           
            //剪枝一下,每一次完成內層的for-loop,立即檢查是否dp[target] == target,優化時間複雜度(26ms -> 20ms)
            if(dp[target] == target)
                return true;
        }
        return dp[target] == target;
    }
}

二维数组版本(易于理解):

public class Solution {
    public static void main(String[] args) {
        int num[] = {1,5,11,5};
        canPartition(num);

    }
    public static boolean canPartition(int[] nums) {
        int len = nums.length;
        // 题目已经说非空数组,可以不做非空判断
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        // 特判:如果是奇数,就不符合要求
        if ((sum %2 ) != 0) {
            return false;
        }

        int target = sum / 2; //目标背包容量
        // 创建二维状态数组,行:物品索引,列:容量(包括 0)
        /*
        dp[i][j]表示从数组的 [0, i] 这个子区间内挑选一些正整数
          每个数只能用一次,使得这些数的和恰好等于 j。
        */
        boolean[][] dp = new boolean[len][target + 1];

        // 先填表格第 0 行,第 1 个数只能让容积为它自己的背包恰好装满  (这里的dp[][]数组的含义就是“恰好”,所以就算容积比它大的也不要)
        if (nums[0] <= target) {
            dp[0][nums[0]] = true;
        }
        // 再填表格后面几行
        //外层遍历物品
        for (int i = 1; i < len; i++) {
            //内层遍历背包
            for (int j = 0; j <= target; j++) {
                // 直接从上一行先把结果抄下来,然后再修正
                dp[i][j] = dp[i - 1][j];

                //如果某个物品单独的重量恰好就等于背包的重量,那么也是满足dp数组的定义的
                if (nums[i] == j) {
                    dp[i][j] = true;
                    continue;
                }
                //如果某个物品的重量小于j,那就可以看该物品是否放入背包
                //dp[i - 1][j]表示该物品不放入背包,如果在 [0, i - 1] 这个子区间内已经有一部分元素,使得它们的和为 j ,那么 dp[i][j] = true;
                //dp[i - 1][j - nums[i]]表示该物品放入背包。如果在 [0, i - 1] 这个子区间内就得找到一部分元素,使得它们的和为 j - nums[i]。
                if (nums[i] < j) {
                    dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
                }
            }
        }
        for (int i = 0; i < len; i++) {
            for (int j = 0; j <= target; j++) {
                System.out.print(dp[i][j]+" ");
            }
            System.out.println();
        }
        return dp[len - 1][target];
    }
}
//dp数组的打印结果
false true false false false false false false false false false false 
false true false false false true true false false false false false 
false true false false false true true false false false false true 
false true false false false true true false false false true true 

二維數組整數版本

class Solution {
    public boolean canPartition(int[] nums) {
        //using 2-D DP array.
        int len = nums.length;
        //check edge cases;
        if(len == 0)
            return false;

        int sum = 0;
        for (int num : nums)
            sum += num;
        //we only deal with even numbers. If sum is odd, return false;
        if(sum % 2 == 1)
            return false;
        
        int target = sum / 2;
        int[][] dp = new int[nums.length][target + 1];

        // for(int j = 0; j <= target; j++){
        //     if(j < nums[0])
        //         dp[0][j] = 0;
        //     else
        //         dp[0][j] = nums[0];
        // }

        //initialize dp array
        for(int j = nums[0]; j <= target; j++){
            dp[0][j] = nums[0];
        }

        for(int i = 1; i < len; 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]);
            }
        }

        //print out DP array
        // for(int x : dp){
        //     System.out.print(x + ",");
        // }
        // System.out.print("    "+i+" row"+"\n");
        return dp[len - 1][target] == target;
    }
}
//dp数组的打印结果 for test case 1.
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
0, 1, 1, 1, 1, 5, 6, 6, 6, 6, 6, 6, 
0, 1, 1, 1, 1, 5, 6, 6, 6, 6, 6, 11, 
0, 1, 1, 1, 1, 5, 6, 6, 6, 6, 10, 11, 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《Numpy 简易速速上手小册》第9章:Numpy 在机器学习中的应用(2024 最新版)

文章目录 9.1 数据预处理9.1.1 基础知识9.1.2 完整案例&#xff1a;数据标准化9.1.3 拓展案例 1&#xff1a;缺失值处理9.1.4 拓展案例 2&#xff1a;非数值数据的转换 9.2 特征提取和处理9.2.1 基础知识9.2.2 完整案例&#xff1a;特征归一化9.2.3 拓展案例 1&#xff1a;特征…

MySQL知识点总结:构建可靠高性能的关系型数据库

摘要&#xff1a;MySQL是一款广泛使用的开源关系型数据库管理系统&#xff0c;具备可靠性和高性能的特点。本文将总结MySQL的一些重要知识点&#xff0c;帮助读者了解如何使用MySQL构建可靠高性能的关系型数据库。 正文&#xff1a; ### 1. 数据类型 MySQL支持多种数据类型&…

SpringBoot整合Activiti7—— 补偿边界/补偿中间事件(十五)

文章目录 补偿边界/补偿中间事件代码实现xml文件测试流程流程执行步骤 补偿边界/补偿中间事件 补偿事件可以被触发来回滚或修复之前已经完成的任务或活动。 补偿事件通常与错误边界事件&#xff08;Error Boundary Event&#xff09;结合使用。当任务或活动发生异常时&#xff…

SQL sever2008中创建用户并赋权

一、创建数据库dream CREATE DATABASE dream; 二、创建登录用户XZS 法一&#xff1a;使用SSMS创建 通过查询 sys.syslogins 系统视图来确定当前登录是否具有系统管理员权限。执行以下查询语句&#xff1a; SELECT name, isntname FROM sys.syslogins WHERE sysadmin 1;选…

Android Studio从零基础到APP上线(3)

第3章 简单控件 本章介绍App开发常见的几类简单控件的用法,主要包括:显示文字的文本视图,容纳视图的常用布局,响应点击的按钮控件,显示图片的图像视图等。然后结合本章所学的知识,演示一个实战项目“简单计算器”的设计与实现。 3.1 文本显示 本节介绍如何在文本视图Tex…

Jmeter,如何从数组参数中取值

有个post请求&#xff0c;参数“equipment_ids”&#xff0c;是个数组&#xff0c;需求每次执行的时候&#xff0c;按顺序取equipment_ids中不同的值 要实现在 JMeter 中每次执行请求时按顺序取不同的 equipment_ids 中的值&#xff0c;你可以使用 Counter 元件来生成索引&…

【面试深度解析】掌上先机后端面试(Java基础能力夯实)

欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复 「资料」 可领取编程高频电子书&#xff01; 在我后台回复「面试」可领取硬核面试笔记&#xff01; 文章导读地址…

HTML音频标签

新增的语义化的标签&#xff1a; 即直接给了一个具象化的盒子。 新增的多媒体标签&#xff1a; 视频格式&#xff1a; 当都不支持的时候会显示文字。 video仍然是可以看成一个盒子。 音频格式&#xff1a; 新增的input 表单控件&#xff1a; 新增的表单属性&#xff1a; 提示文…

MyBatis 的XML实现方法

MyBatis 的XML实现方法 MyBatis 的XML实现方法前情提示创建mapper接口添加配置创建xml文件操作数据库insert标签delete标签select标签resultMap标签 update标签sql标签,include标签 MyBatis 的XML实现方法 前情提示 关于mybatis的重要准备工作,请看MyBatis 的注解实现方法 创…

Java SWT Composite 绘画

Java SWT Composite 绘画 1 Java SWT2 Java 图形框架 AWT、Swing、SWT、JavaFX2.1 Java AWT (Abstract Window Toolkit)2.2 Java Swing2.3 Java SWT (Standard Widget Toolkit)2.4 Java JavaFX 3 比较和总结 1 Java SWT Java SWT&#xff08;Standard Widget Toolkit&#xff…

Power BI案例-链接Mysql方法

Power BI案例-连锁Mysql 方法1-通过组件mysql-connector-net-8.3.0&#xff1a; 选择文件–获取数据–选择MySQL数据库–选择链接 提示无组件&#xff0c;选择了解详细情况 弹出浏览器&#xff0c;选择下载 不用登陆&#xff0c;可以直接下载 下载的组件如下&#xff1a…

【开源】基于JAVA+Vue+SpringBoot的陕西非物质文化遗产网站

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 设计目标2.2 研究内容2.3 研究方法与过程2.3.1 系统设计2.3.2 查阅文献2.3.3 网站分析2.3.4 网站设计2.3.5 网站实现2.3.6 系统测试与效果分析 三、系统展示四、核心代码4.1 查询民间文学4.2 查询传统音乐4.3 增改传统舞…

代码随想录算法训练营Day46|139.单词拆分、多重背包理论基础、背包问题总结

目录 139.单词拆分 方法一&#xff1a;回溯法 算法实现 方法二&#xff1a;背包问题 算法实现 多重背包理论基础 思路 算法实现 背包问题总结 前言 背包递推公式 遍历顺序 0-1背包 完全背包 139.单词拆分 题目链接 文章链接 方法一&#xff1a;回溯法 在回溯专题…

Endnote常见设置(硕士毕业论文参考文献修改)

1、根据大多数期刊或学校使用的标准&#xff0c;英文名首字母大写后续字母小写。 2、需要手动调整Endnote中的参考文献相关内容 3、关于姓名大小写设置 AS IS是不更改大小写&#xff0c;EndNote库中文献的大小是什么样&#xff0c;Word中就显示什么样。选择Normal为首字母大…

HDMI2.1之eARC简介-Dolby Atmos和DTS:X

文章目录 eARC目的更大的带宽更高质量音频支持对象型音频与CEC&#xff08;Consumer Electronics Control&#xff09;的兼容性&#xff1a; 适应流媒体发展Dolby AtmosDTS:X高分辨率音频更高的音频位深度和采样率低延迟音频 对象型音频格式独立对象三维定位动态音场适应性和灵…

嵌入式——串行外围设备接口(SPI)

目录 一、初识SPI 1. 介绍 2. 特性 补&#xff1a; 二、物理层 1. SS &#xff08;Slave Select&#xff09; 2. SCK &#xff08;Serial Clock&#xff09; 3. MOSI &#xff08;Master Output, Slave Input&#xff09; 4. MISO &#xff08;Master Input&#xff0…

虚拟机Windows Server 2016 安装 MySQL8

目录 一、下载MySQL8 1.下载地址&#xff1a; 2.创建my.ini文件 二、安装步骤 第一步&#xff1a;命令窗口 第二步&#xff1a;切换目录 第三步&#xff1a;安装服务 第四步&#xff1a;生成临时密码 第五步&#xff1a;启动服务 第六步&#xff1a; 修改密码 三…

【Linux系统化学习】进程替换

目录 进程程序替换 替换原理 ​编辑替换函数 函数解释 命名理解 函数使用 execl execlp execv execvp 调用其它程序 进程程序替换 替换原理 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个…

0203-2-输入输出系统

第六章&#xff1a;输入输出系统 I/O系统的功能&#xff0c;模型和接口 I/O系统管理的对象是I/O设备和相应的设备控制器。 I/O系统的基本功能 隐藏物理设备的细节与设备的无关性提高处理机和I/O设备的利用率对I/O设备进行控制确保对设备的正确共享错误处理 I/O软件的层次结…

重写Sylar基于协程的服务器(4、协程调度模块的设计)

重写Sylar基于协程的服务器&#xff08;4、协程调度模块的设计&#xff09; 重写Sylar基于协程的服务器系列&#xff1a; 重写Sylar基于协程的服务器&#xff08;0、搭建开发环境以及项目框架 || 下载编译简化版Sylar&#xff09; 重写Sylar基于协程的服务器&#xff08;1、日…