背包问题总结
🎐

背包问题总结

text
背包问题
Tags
动态规划
刷题
算法题
Created
Sep 5, 2022 04:31 AM
有 n 件物品和一个最多能背重量为 w 的背包。第 i 件物品的重量是 weight[i],得到的价值是value[i] ,且每件物品只能使用一次,求解将哪些物品装入背包里物品价值总和最大,最大为多少?
notion image
如上述,每个物品只能使用一次的为“01背包问题”;相反,使用次数无限制的称为“完全背包问题”;

经典题目

分割等和子集

给你一个只包含正整数非空数组nums。请判断是否可以将这个数组分割为两个子集,使得两个子集的元素和相同
# 示例: """ 输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。 输入:nums = [1,2,3,5] 输出:false 解释:数组不能分割成两个元素和相等的子集。 """ class Solution: def canPartition(self, nums: List[int]) -> bool: sum_nums = sum(nums) # 统计sum if sum_nums % 2 == 1: return False target = sum_nums // 2 # 此时背包大小为target dp = [False] * (target + 1) dp[0] = True for num in nums: if num > target: return False for j in range(target, num-1, -1): dp[j] = dp[j-num] | dp[j] # 判断是否可以实现当前数值 return dp[-1]

思路

  • 先将题目进行理解和转换:
    • 元素和为奇数时一定不能成立,返回False;
    • 将问题转化为显而易见的背包问题,即背包大小 target=sum/2,nums中元素可表示为重量
  • 利用背包问题的解法进行求解:
    • 本题相当于背包里放入物品,理解为物品i的重量是nums[i],其价值也是nums[i];
    • 递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
    • 确定遍历顺序:如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历;

背包递推公式

问能否装满背包(或者最多装多少):dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);其中weight和value值相同,动态数组dp中记录value
问装满背包有几种方法(不限次数):dp[j] += dp[j - nums[i]] ;对应题目如下:
问背包能装满最大价值:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); 对应题目如下:
问装满背包所有物品的最小个数:dp[j] = min(dp[j - coins[i]] + 1, dp[j]); 对应题目如下:

遍历顺序

01背包

分为二维数组和一维数组实现方式,其中二维数组01背包先遍历物品/背包都可以,并且第二层循环是从小到大遍历的;一维数组01背包只能先遍历物品再遍历背包容量,并且第二层循环是从大到小遍历;
Tip:一维数组想要实现01背包问题需要的限制比较多,因此面对01问题时,可以先从”先物品后背包+第二层循环从大到小遍历“的角度进行思考。

完全背包

对于完全背包问题,先遍历物品/背包都可,且都是从小到大遍历;但是对于不同的问题,两个for循环的先后顺序会不一样:如果求组合数就是外层for循环遍历物品,内层for遍历背包;如果求排列数就是外层for遍历背包,内层for循环遍历物品;

总结

  • 递推公式可以参考总结地几种公式进行套用,但关键还是在于对于题目地理解来快速确定递推公式;
  • 遍历顺序:遍历顺序通常需要考虑两个方面的遍历;
    • 先遍历物品/背包:针对不同的问题需要考虑不同的遍历顺序;针对01背包中一维/二维数组的选择页需要考虑不同的遍历顺序;
    • 从大到小/从小到大遍历:其实特殊情况就在于01背包中一维数组实现时需要进行从大到小遍历,其余都从小到大遍历即可;
  • LeetCode中题型多为中等,主要因为题解无外乎几种情况,可以一一进行分析,掌握后就可以快速解出题目;

参考

💡
参考链接网站中有非常详细的讲解,建议认真阅读学习