leetcode——背包问题汇总

        本章来汇总一下leetcode中做过的背包问题,包括0-1背包完全背包

        背包问题的通常形式为:有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。求解将哪些物品装入背包里物品价值总和最大。0-1背包和完全背包的区别就在于物品能否重复拿取

        但是一般题目不会明确告诉你是背包问题,需要自己将问题进行转化。下面汇总一些常见的0-1背包和完全背包问题。

0-1背包问题

1、分割等和子集(★)

         本题题意可以转化为:集合中能否出现总和为 sum / 2 的子集。 进而可以使用背包问题的思想来做:数组为背包,元素为物品。 又因为物品不可重复取,所以是0-1背包问题

        本题使用0-1背包动态规划算法思路如下:

  • 先求出nums数组的元素总和sum, (sum/2) 即为背包的大小。
  • 设dp数组,长度为sum/2 , 初始化为全0。dp[i]含义为:容量为i的背包最大能装多重的物品。
  • 先遍历物品再遍历背包,并且为了防止物品重复放入,背包需要倒序遍历。
  • 最后判断dp[target] 是否等于target,是则返回true。

        java代码如下:

class Solution {public boolean canPartition(int[] nums) {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];Arrays.fill(dp,0); //初始化dp数组为全0//先遍历物品再遍历背包for(int i=0; i<nums.length; i++){for(int j=target; j>=nums[i]; j--){dp[j] = Math.max(dp[j] , dp[j-nums[i]] + nums[i]);}}if(dp[target] == target) return true;return false;}
}

2、最后一块石头的重量II

        本题类似于上一题,可以转化为0-1背包问题。区别在于:本题如果可以分成等分量的两个子集,返回0;如果不能,则需要返回两子集的最小差值。 

        按照上一题的思路,java代码如下:

class Solution {public int lastStoneWeightII(int[] stones) {int sum = 0;for(int stone : stones){sum += stone;}int target = sum / 2;int[] dp = new int[target+1];Arrays.fill(dp,0);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]);}}return sum-dp[target]*2;}
}

3、目标和(★)

        本题难点在于如何将题意转化为0-1背包问题。 可以这么想:将nums数组分为两个子集:子集一全加正号,子集二全加负号,然后像上题一样,两子集相碰撞(相减),结果为target值。设sum为总元素和,positive_sum为正数和,那么(sum-positive_sum)就为负数和,有如下等式成立:positive_sum - (sum-positive_sum) = target , 解得positive_sum = (target + sum) / 2 。

        此时成功将题意转化为0-1背包问题:positive_sum为背包大小,nums中元素为物品。 之前是要求背包所装物品的最大重量,而本题区别在于要求能装满背包的方法个数。整体思路不变,区别就在于递归公式要变化一下:

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

        这个递推公式用于求解装满背包的最大方法个数,可以记一下。

那么设的dp数组含义就变为:dp[i]表示装满大小为 i 的背包的方法个数

java代码如下:

class Solution {public int findTargetSumWays(int[] nums, int target) {int sum = 0;for(int num : nums){sum += num;}if((target + sum) % 2 != 0) return 0; //没有办法得到运算结果targetint positive_sum = (target + sum) / 2; //正数和if(positive_sum < 0) return 0;//正数和不为负数int[] dp = new int[positive_sum+1];dp[0] = 1;for(int i=0; i<nums.length; i++){for(int j=positive_sum; j>=nums[i]; j--){dp[j] = dp[j] + dp[j-nums[i]];}}return dp[positive_sum];}
}

完全背包问题

1、零钱兑换II

        分析题意,有两个关键点:①求得是组合数,组合和排列不一样,组合没有顺序,而排列有。②硬币有无限个,因此本题为完全背包问题。 背包为总金额,物品为硬币,要求的是装满背包有多少种方法,和 上一题“目标和”要求的东西一样,因此递推公式为:

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

        本题是完全背包问题,物品个数为无限,因此遍历的第二层循环不是倒序遍历了,而是正序。(当初倒序遍历就是为了防止重复拿取物品,完全背包可以重复拿取物品)。

        遍历顺序也有讲究,先遍历物品再遍历背包求得是组合数,先遍历背包再遍历物品求得是排列数,此题要求组合数,因此先遍历物品再遍历背包。

        java代码如下:

class Solution {public int change(int amount, int[] coins) {int[] dp = new int[amount+1];dp[0] = 1;for(int i=0; i<coins.length; i++){for(int j=coins[i]; j<=amount; j++){dp[j] = dp[j] + dp[j-coins[i]];}}return dp[amount];}
}

2、组合总和IV

        有了上述题的基础,本题可以说是手拿把掐了。 

        首先物品可以重复拿取,是完全背包问题题目求的是排列数(虽然题目说的组合数,但根据示例来看其实是求排列数),因此遍历顺序应该是先遍历背包再遍历物品 最后求的是装满背包的方法数,所以递推公式是:dp[j] = dp[j] + dp[j-nums[i]]。

        java代码如下:

class Solution {public int combinationSum4(int[] nums, int target) {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] + dp[i-nums[j]];}}} return dp[target];}
}

3、爬楼梯(★)

        本题是动态规划系列梦开始的地方,一开始使用斐波那契数列做的,现在一看就是个完全背包问题,背包大小是总楼梯阶数n,物品是1或2。 要求装满背包的方法个数。

       和上题可以说一毛一样,直接上代码:

class Solution {public int climbStairs(int n) {int dp[] = new int[n+1];dp[0] = 1;for(int i=0; i<=n; i++){for(int j=1; j<=2; j++){if(i >= j){dp[i] = dp[i] + dp[i-j];}}}return dp[n];}
}

4、零钱兑换(★)

        本题属于完全背包问题,由于题目求得是凑成总金额所需得最少硬币个数,所以求组合数和排列数都可以,都不影响最后得最少硬币个数,因此遍历顺序先物品或者先背包都是可以的。

        本题由于求的是最少硬币个数,我们将dp[i]定义为:装满大小为 i 的背包所需最少物品个数。 递归公式为:

dp[j] = Math.min(dp[j] , dp[j-coins[i]]+1);

        此外,初始化dp数组的时候除了dp[0] 初始化为0之外,其他都要初始化为最大值。

        java代码如下:

class Solution {public int coinChange(int[] coins, int amount) {int[] dp = new int[amount+1];//dp[j]:装满大小为j的背包所需的最少硬币个数Arrays.fill(dp,Integer.MAX_VALUE);dp[0] = 0;for(int i=0; i<coins.length; i++){for(int j=coins[i]; j<=amount; j++){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];}
}

5、完全平方数(★)

        本题和 “零钱兑换”非常相似,同属于完全背包,并且求的也是装满背包所需的最小物品数。

        本题的背包为给定的整数n,物品为自然数的平方,如1的平方、2的平方......

        思路同上一题,java代码如下:

class Solution {public int numSquares(int n) {//背包:整数n | 物品:完全平方数的平方int[] dp = new int[n+1]; //dp[i]:装满大小为i的背包所需的最小物品数Arrays.fill(dp,Integer.MAX_VALUE);dp[0] = 0;for(int i=1; i<=n; i++){for(int j=i*i; j<=n; j++){if(dp[j-i*i] != Integer.MAX_VALUE){dp[j] = Math.min(dp[j] , dp[j-i*i]+1);}}}return dp[n];}
}

6、单词拆分(★)

        本题由于单词可以重复使用,因此是完全背包问题背包为字符串s,物品为wordDict里的字符串。   要求使用物品能否装满背包。 因此设dp数组,dp[i]的含义为:大小为i的背包能否被物品装满 由于物品(单词)之间有顺序之分,因此本题属于排列问题,先遍历背包再遍历物品

        直接看java代码:

class Solution {public boolean wordBreak(String s, List<String> wordDict) {int capacity = s.length();int[] dp = new int[capacity+1]; //dp[i]:大小为i的背包能否被物品装满dp[0] = 1;for(int i=1; i<=capacity; i++){for(String word : wordDict){int len = word.length();if(i>=len && dp[i-len]==1 && word.equals(s.substring(i-len,i))){dp[i] = 1;break;}}}return dp[capacity] == 1? true : false;}
}

总结

        以上题目过关背包问题基本上就没问题了,最后总结一下背包问题的套路

  • 0-1背包问题遍历背包的时候需要倒序遍历,以防止重复拿取物品。完全背包可以重复拿取物品,因此是正序遍历。
  • 题目求组合数和排列数的遍历顺序是不一样的:求组合数要先遍历物品再遍历背包,求排列数要先遍历背包再遍历物品。
  • 递归公式根据题目要求不一样而不一样,下面列举常见的几个递推公式:
    • 求尽可能装满背包的最大重量:dp[j] = Math.max(dp[j] , dp[j-nums[i]] + nums[i]); 如0-1背包的第1、2题。
    • 求装满背包的方法种数:dp[j] = dp[j] + dp[j-nums[i]]; 如0-1背包的第3题、完全背包的第1题、第2题、第3题。
    • 求装满背包的最小物品数:dp[j] = Math.min(dp[j] , dp[j-nums[i]]+1); 如完全背包的第4题、第5题。
    • 单词拆分这题单独记一下吧。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[密码学基础][每个信息安全博士生应该知道的52件事]52.先进的应用概念 系统的大致安全需求

这是一系列博客文章中最新的一篇&#xff0c;该文章列举了“每个博士生在做密码学时应该知道的52件事”:一系列问题的汇编是为了让博士生们在第一年结束时知道些什么。我们希望学生知道从理论到实践的各个方面。但关键是你需要在密码学中考虑的不仅是对遵守规则的玩家的安全&am…

spring学习(32):使用junit4测试

目录结构 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/P…

[dp]Leetcode 5. Longest Palindromic Substring

输入&#xff1a;一个字符串s 输出&#xff1a;最长的回文子串 规则&#xff1a;“abba"是一个回文 分析&#xff1a;输入是"babad”&#xff0c;输出"bab"。这个问题不能再按照之前分段的思路解决&#xff0c;或者说完全按照之前的思路。 之前的思路是&a…

[密码学基础][每个信息安全博士生应该知道的52件事][Bristol52]38.隐蔽信道和侧信道的区别

这是一系列博客文章中最新的一篇&#xff0c;该文章列举了“每个博士生在做密码学时应该知道的52件事”:一系列问题的汇编是为了让博士生们在第一年结束时知道些什么。 隐蔽信道和侧信道是两种不同的信息泄露信道 隐蔽信道使用目的不是通信的机制。例如&#xff0c;写和检查文…

NFA和DFA的区别

NFADFA初始状态不唯一唯一弧上的标记字(单字符字/ε)字符(串)转换关系非确定确定对于每个NFA M都存在一个DFA M 使得 L(M) L(M) 转载于:https://www.cnblogs.com/masterchd/p/11061281.html

spring学习(33):id和name

目录结构 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/P…

线性回归与 logistic回归

线性回归 算法方程&#xff1a;hθ(x)∑i0nθixiθTxh_{\theta}(x)\sum_{i0}^{n} \theta_{i} x_{i}\theta^{T} xhθ​(x)∑i0n​θi​xi​θTx 损失函数&#xff1a;J(θ0,θ1,…,θn)12m∑i1m(hθ(x(i))−y(i))2J\left(\theta_{0}, \theta_{1}, \ldots, \theta_{n}\right)\f…

[高效时间管理] 番茄工作钟 windows版本

【背景】 2019年本人学会了记录每日时间&#xff08;将每日分割成半小时一段的时间&#xff09; &#xff0c;但似乎是为了完成而完成&#xff0c;效果不佳&#xff0c;手机端的番茄钟总是诱惑太多&#xff0c;就在准备tb计时器的时候&#xff0c;发现了宝藏软件。 个人整理知…

IDEA设置取消自动显示参数提示

IDEA设置取消自动显示参数提示 最近在使用IDEA的过程中&#xff0c;发现方法中一直显示形参名的提示&#xff0c;无法选中&#xff0c;也无法删除&#xff0c;基于不同人的使用习惯不同&#xff0c;有的人不喜欢这种提示&#xff0c;我也在网上寻找各种解决方案&#xff0c;由于…

spring学习(34):构造函数依赖注入

目录结构 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/P…

[高效时间管理]复盘篇

【背景】 2019年本人在公众号中阅读了《时间管理&#xff0c;这篇就够了》&#xff0c;学习了其中的内容并开始记录每日所做&#xff0c;偶尔晚上进行复盘&#xff0c;对督促学习和反思起到一定的作用&#xff0c;故向大家分享经验。 为什么要时间管理&#xff1f; 时间管理…

PHP内存管理机制与垃圾回收机制

转载&#xff1a;https://www.cnblogs.com/zk0533/p/5667122.html PHP内存管理机制 var_dump(memory_get_usage()); //获取内存 $a "laruence"; //定义一个变量 var_dump(memory_get_usage()); //定义变量之后获取内存 unset($a); …

139. Word Break

目录题目描述分析暴力搜索记忆化回溯动态规划题目描述 给定一个字符串数组作为词典&#xff0c;再给定一个字符串。判断一下用词典中的词是不是可以组成这个字符串。 注意&#xff1a;词典中的词可以使用多次&#xff1b;词典中不存在重复的词 例如&#xff1a; 输入: s “le…

spring学习(35):c名称空间注入

目录结构 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/P…

Element-UI 表单验证规则rules 配置参数说明

官方文档 : https://github.com/yiminghe/async-validator转载于:https://www.cnblogs.com/itstac/p/11063125.html

[能力提升][费曼学习法]学习方法

【背景知识】 费曼学习法 费曼学习法可以简化为四个单词&#xff1a;Concept &#xff08;概念&#xff09;、Teach &#xff08;教给别人&#xff09;、Review &#xff08;评价&#xff09;、Simplify &#xff08;简化&#xff09;。 第一步&#xff1a;把它教给一个小孩…

338. Counting Bits

输入&#xff1a;一个正整数n 输出&#xff1a;一个数组 规则&#xff1a;输出的数组分别表示0<x<n0<x<n0<x<n&#xff0c;范围内x的二进制表示中有多少个1。 示例&#xff1a;输入2&#xff0c;输出[0,1,1]。 分析&#xff1a;这道题目很直观。如果计算数字…

spring学习(36):注入简单类型

目录结构 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/P…

【剑指offer】【leetcode精选题集】【Java】剑指offer题解合集 更新中

Leetcode题集 [剑指offer][JAVA]面试题第[03]题[数组中的重复数字][HashSet] [剑指offer][JAVA]面试题第[04]题[二维数中的查找][数组] [剑指offer][JAVA]面试题第[05]题[替换空格][StringBuilder/Buffer] [剑指offer][JAVA]面试题第[06]题[从尾到头打印链表][栈][递归] [剑指…

41. First Missing Positive

输入&#xff1a;一个没有排序的正数数组nums 输出&#xff1a;在 nums数组中未出现的最小的正整数 规则&#xff1a;数组中的元素可能是负数&#xff0c;也可能重复。要求时间复杂度O(n)&#xff0c;空间复杂度O(1)。 分析&#xff1a;题目其实很简单&#xff0c;遍历一次放入…