背包问题详解与力扣上的背包问题(待更)

本文详细介绍了0-1背包问题和完全背包问题,包括问题分析、算法设计、完美图解、伪代码和实战演练。通过讲解《算法笔记》和《背包九讲》的内容,阐述了递推公式和优化方法。文中还通过LeetCode的具体题目,如分割等和子集、一和零、目标和,展示了如何应用0-1背包和完全背包的解题思路。此外,文章讨论了背包问题的初始化策略,特别是求最优解和方案数时的初始化方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1 0-1背包问题

1.1 问题分析

1.2 算法设计

1.3 完美图解

1.4 伪代码详解

1.5 实战演练

1.6 算法解析和优化拓展

重点注意

1.7 LeetCode上的0-1背包问题

分割等和子集(medium难度)(0-1背包问题,背包问题的基础)

一和零(medium难度)(0-1背包问题)

目标和(medium难度)(0-1背包问题)

2 完全背包问题详解

2.1 《算法笔记》关于完全背包问题的讲解

2.2 《背包九讲》关于完全背包问题的讲解

2.3 完全背包求最优解(不需要装满)的递推公式

2.4 完全背包解决排列组合问题(恰好装满)的递推公式

2.5 LeetCode上的完全背包问题

零钱兑换(medium难度)

零钱兑换Ⅱ(medium难度)(求方案数)

3 背包问题的初始化

3.1 求最优解的背包问题中:装满/不需要装满对应的初始化

3.2 求方案数的背包问题中:恰好装满(或装到指定容量)对应的初始化


1 0-1背包问题

1.1 问题分析

1.2 算法设计

1.3 完美图解

1.4 伪代码详解

1.5 实战演练

1.6 算法解析和优化拓展

注:上方图片来自陈小玉的《趣学算法》(P210-P220)

重点注意

算法定义部分:

 

算法优化部分:

1.7 LeetCode上的0-1背包问题

分割等和子集(medium难度)(0-1背包问题,背包问题的基础)

https://leetcode-cn.com/problems/partition-equal-subset-sum/

本题方法思路和代码来源:
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/
来源:力扣(LeetCode)

参考代码1:

public class Solution {

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

        int target = sum / 2;
        // 创建二维状态数组,行:物品索引,列:容量(包括 0)
        boolean[][] dp = new boolean[len][target + 1];

        // 先填表格第 0 行,第 1 个数只能让容积为它自己的背包恰好装满
        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];
                
                //j恰好等于nums[i],即单独nums[j] 这个数恰好等于此时j(背包的容积)
                if (nums[i] == j) {
                    dp[i][j] = true;
                    continue;
                }
//不选择nums[i],如果在[0, i - 1]这个子区间内已经有一部分元素,使得它们的和为j ,那么dp[i][j] = true;
//选择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]];
                }
            }
        }
        return dp[len - 1][target];
    }
}

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/
来源:力扣(LeetCode)

参考代码2:

public class Solution {

    public boolean canPartition(int[] nums) {
        int len = nums.length;
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if ((sum & 1) == 1) {
            return false;
        }

        int target = sum / 2;
        boolean[][] dp = new boolean[len][target + 1];
        
        // 初始化成为 true 虽然不符合状态定义,但是从状态转移来说是完全可以的
        dp[0][0] = true;

        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];
                if (nums[i] <= j) {
                    dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
                }
            }

            // 由于状态转移方程的特殊性,提前结束,可以认为是剪枝操作
            if (dp[i][target]) {
                return true;
            }
        }
        return dp[len - 1][target];
    }
}

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/
来源:力扣(LeetCode)

这里可能会有人困惑为什么压缩到一维时,要采用逆序。因为在一维情况下,是根据

dp[j] || dp[j - nums[i]]

来推d[j]的值,如不逆序,就无法保证在外循环 i 值保持不变 j 值递增的情况下,dp[j - num[i]]的值不会被当前所放入的nums[i]所修改,当 j 值未到达临界条件前,会一直被nums[i]影响,也即是可能重复的放入了多次nums[i],为了避免前面对后面产生影响,故用逆序。
举个例子:

数组为[2,2,3,5],要找和为6的组合,i = 0时,dp[2]为真,当i自增到1,j = 4时,nums[i] = 2,dp[4] = dp[4] || dp[4 - 2]为true,当i不变,j = 6时,dp[6] = dp [6] || dp [6 - 2],而dp[4]为true,所以dp[6] = true,显然是错误的。 故必须得纠正在正序情况下,i值不变时多次放入nums[i]的情况。

参考代码3:只展示了使用一维表格,并且从后向前填表格的代码。

public class Solution {

    public boolean canPartition(int[] nums) {
        int len = nums.length;
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值