刷题第42, 43天 | 416. 分割等和子集、1049. 最后一块石头的重量 II、494. 目标和、474.一和零

416. Partition Equal Subset Sum

题目链接:416. Partition Equal Subset Sum
思路链接:代码随想录动态规划-分割等和子集

思路

转化为01背包问题需要一下元素:背包最大重量、物品重量、物品价值
这道题就是尽可能将数组分割成两个等和的数组,因此只要将数组sum除以2得到的值作为背包的最大重量就行了。至于物品负重和价值,这里最重要的点就是物品重量与价值看作是相等的!

  • 背包负重(target):sum(nums) / 2
  • 物品负重:sum[i]
  • 物品价值:sum[i] 与负重相同
  1. 定义dp数组: 总容量是j的背包能获得的最大价值是dp[j]
  2. 确定递推关系式:dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]); 一维数组方法
    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]); 二维数组方法
  3. 初始化dp数组:dp[0]一定为0,因为负重为0;之后的要取最小非负整数,因为不能覆盖住dp[j - nums[i]] + nums[i]
  4. 遍历顺序:对于一维数组来说,一定要先遍历物品,再倒序遍历背包。每一个元素一定是不可重复放入,所以从大到小遍历。可以通过画二维数组来理解这里为什么要倒序遍历。
  5. 打印dp数组

Code

class Solution {
    public boolean canPartition(int[] nums) {
        // 利用01背包问题的思路
        // 背包负重(target):sum(nums) / 2
        // 物品负重:sum[i]
        // 物品价值:sum[i] 与负重相同

        // 确定target
        int sum = 0;
        for (int i : nums) {
            sum += i;
        }
        if (sum % 2 == 1) {
            return false;
        }
        int target = sum / 2;

        // 1. 定义dp数组: 总容量是j的背包能获得的最大价值是dp[j]
        int[] dp = new int[target + 1];
        // 2. 确定递推关系式
        // dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]); 一维数组方法
        // dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]); 二维数组方法
        // 3. 初始化dp数组
        // dp[0]一定为0,因为负重为0;之后的要取最小非负整数,因为不能覆盖住dp[j - nums[i]] + nums[i]
        // 4. 遍历顺序
        for (int i = 0; i < nums.length; i++) {
            for (int j = target; j >= nums[i]; j--) {
                // System.out.print("nums.length: " + nums.length + " i: " + i + " j: " + j);
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        // 5. 打印dp数组
        // for (int i : dp) {
        //     System.out.print(i + " ");
        // }
        if (dp[target] == target) {
            return true;
        } else {
            return false;
        }
    }
}

1049. Last Stone Weight II

题目链接:1049. Last Stone Weight II
思路链接:代码随想录动态规划-最后一块石头的重量 II

思路

把问题转化为01背包问题:为了得到剩下石头的最小重量,就只要尽可能将数组平均分成两组。注意,这里也是要将物品重量与物品价值相等。

  • 背包最大负重 j: sum(stones) / 2
  • 物品 i重量: stones[i]
  • 物品 i价值: stones[i]
  1. 确定dp数组:最大载重为j的背包能获得的最大价值为dp[j]
  2. 递推公式:dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
  3. 初始化数组:dp[0]一定为0,后面因为不能覆盖其他值,也要为0
  4. 遍历方向:一维数组先遍历物品,再倒序遍历背包
  5. 打印数组

Code

class Solution {
    public int lastStoneWeightII(int[] stones) {
        // 思路:把数组分为尽可能平均的两组
        // 背包最大负重 j: sum(stones) / 2
        // 物品 i重量: stones[i]
        // 物品 i价值: stones[i]

        // 确定target负重
        int sum = 0;
        for (int i : stones) {
            sum += i;
        }
        int target = sum / 2;
        // 1. 确定dp数组
        // 最大载重为j的背包能获得的最大价值为dp[j]
        int[] dp = new int[target + 1];
        // 2. 递推公式
        // dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
        // 3. 初始化数组
        // dp[0]一定为0,后面因为不能覆盖其他值,也要为0
        // 4. 遍历方向
        for (int i = 0; i < stones.length; i++) {
            for (int j = target; j >= stones[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
            }
        }
        // 5. 打印dp数组
        // for (int i : dp) {
        //     System.out.print(i + " ");
        // }
        return sum - dp[target] - dp[target];
    }
}

494. Target Sum

题目链接:494. Target Sum
思路链接:代码随想录动态规划-目标和

思路

转化为01背包问题:还是需要分成两个部分:做加法的部分和做减法的部分。
做加法的总和为x,做减法的总和为sum - x,得到target = x - (sum - x),于是x = (target + sum) / 2
所以将问题转化为 装满重量为x的背包,最多有几种方法

  1. 确定dp数组:装满最大重量为j的背包,最多有dp[j]种方法 一维数组;将序号为[0, i]的物品nums[i]装满最大重量为j的背包,最多有dp[i][j]种方法 二维数组
  2. 确定递推公式:只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法;dp[j] += dp[j - nums[i]];
  3. 初始化数组:背包重量为0时,只有一种方法 dp[0] = 1, 其他为0, 因为都是要由dp[0]推导过来
  4. 遍历顺序:先遍历物品,在倒序遍历背包
  5. 打印数组

Code

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        // 做加法的总和为x
        // 做减法的总和为sum - x
        // 得到target = x - (sum - x)
        // x = (target + sum) / 2
        // -> 装满重量为x的背包,最多有几种方法
        int sum = 0;
        for (int i : nums) {
            sum += i;
        }
        if (Math.abs(target) > sum ) {
            return 0;
        }
        if ((target + sum) % 2 == 1) {
            return 0;
        }
        int maxWeight = (target + sum) / 2;
        // 1. 确定dp数组
        // 装满最大重量为j的背包,最多有dp[j]种方法 一维数组
        // 将序号为[0, i]的物品nums[i]装满最大重量为j的背包,最多有dp[i][j]种方法 二维数组
        int[] dp = new int[maxWeight + 1];
        // 2. 确定递推公式
        // 只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法
        // dp[j] += dp[j - nums[i]];
        // 3. 初始化数组
        // 背包重量为0时,只有一种方法 dp[0] = 1, 其他为0, 因为都是要由dp[0]推导过来
        dp[0] = 1;
        // 4. 遍历顺序
        for (int i = 0; i < nums.length; i++) {
            for (int j = maxWeight; j >= nums[i]; j--) {
                // System.out.print(dp[j - nums[i]] + " ");
                dp[j] += dp[j - nums[i]];
            }
        }
        // 5. 打印数组
        // for (int i : dp) {
        //     System.out.print(i + " ");
        // }
        return dp[maxWeight];
    }
}

474. Ones and Zeroes

题目链接:474. Ones and Zeroes
思路链接:代码随想录动态规划-一和零

思路

还是转化成01背包问题,只不过现在的背包有两个维度了,因此要用二维数组表示。
现在的背包最大重量有两个维度:m个0, n个1
问题转化成利用下标为[0, i]的物品strs[i]装入背包,装满最大重量为m,n的背包最多需要几个物品
物品重量: x个0,y个1

  1. 定义dp数组:最多有i个0和j个1的strs的最大子集的大小为dp[i][j] (装满重量为i,j的背包最多需要dp[i][j]个物品)
  2. 确定递推公式:dp[i][j] = Math.max(dp[i][j], dp[i - x][j - y] + 1); 装入背包物品个数+1
  3. 初始化数组:背包重量为0时,显然是0个物品;为了防止初始化太大的正数,导致dp[i - x][j - y] + 1无法覆盖dp[i][j],后面的背包也都初始化为0
  4. 遍历顺序:先遍历物品,然后需要统计每个物品,也就是字符串中0和1的数量,然后倒序遍历背包
  5. 打印数组

Code

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        // 现在的背包最大重量有两个维度:m个0, n个1
        // 问题转化成利用下标为[0, i]的物品strs[i]装入背包,装满最大重量为m,n的背包最多需要几个物品
        // 物品重量: x个0,y个1

        // 1. 定义dp数组
        // 最多有i个0和j个1的strs的最大子集的大小为dp[i][j] (装满重量为i,j的背包最多需要dp[i][j]个物品)
        int[][] dp = new int[m + 1][n + 1];
        // 2. 确定递推公式
        // dp[i][j] = Math.max(dp[i][j], dp[i - x][j - y] + 1); 装入背包物品个数+1
        // 3. 初始化数组
        // 背包重量为0时,显然是0个物品;为了防止初始化太大的正数,导致dp[i - x][j - y] + 1无法覆盖dp[i][j],后面的背包也都初始化为0
        // 4. 遍历顺序
        for (String str : strs) { // 首先遍历物品
            int x = 0;
            int y = 0;
            for (int index = 0; index < str.length(); index++) { // 统计物品重量:0 和 1 的个数
                if (str.charAt(index) == '0') {
                    x++;
                } else {
                    y++;
                }
            }
            for (int i = m; i >= x; i--) { // 倒序遍历背包
                for (int j = n; j >= y; j--) {
                    dp[i][j] = Math.max(dp[i][j], dp[i - x][j - y] + 1);
                }
            }
        }
        // 5. 打印dp数组
        // for (int i = 0; i < dp.length; i++) {
        //     for (int j = 0; j < dp[0].length; j++) {
        //         System.out.print(dp[i][j] + " ");
        //     }
        //     System.out.println("");
        // }
        return dp[m][n];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值