46.携带研究材料
题目
小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。
小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。
第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。
第二行包含 M 个正整数,代表每种研究材料的所占空间。
第三行包含 M 个正整数,代表每种研究材料的价值。
输出一个整数,代表小明能够携带的研究材料的最大价值。
代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int m = sc.nextInt(); //物品种类
int n = sc.nextInt(); // 背包容量
int[] weight = new int[m]; //每个物品的重量
int[] value = new int[m]; //每个物品的价值
for(int i=0; i < m; i++){
weight[i] = sc.nextInt();
}
for(int i=0; i < m; i++){
value[i] = sc.nextInt();
}
//dp[i][j]表示从下标0-i中任取物品,放入容量为n的背包的最大价值
int[][] dp = new int[m][n+1]; //dp数组
//dp数组初始化
//第一列初始化,背包容量=0,dp全为0
for(int i=0; i < m; i++){
dp[i][0] = 0;
}
//第一行初始化,背包容量0-n,取物品0
for(int j=0; j <= n; j++){
//背包容量不够,不能取物品0
if(j < weight[0]){
dp[0][j] = 0;
}
else{
dp[0][j] = value[0]; //可以取物品0
}
}
//遍历顺序:从上到小+从左到右(先物品再背包)
for(int i=1; i < m; i++){
for(int j=1; j <= n; j++){
//j-weight[i]小于0,下标越界,容量单独放i都不够,肯定不能取物品i
if(j - weight[i] < 0){
dp[i][j] = dp[i-1][j];
}
else{
int no = dp[i-1][j]; //不取物品i:当前容量j的背包放不下物品i
int yes = dp[i-1][j-weight[i]] + value[i]; //取物品i:容量j的背包能放下物品i
dp[i][j] = Math.max(no, yes);
}
}
}
System.out.println(dp[m-1][n]);;
}
}
416.分割等和子集
题目
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5] 输出:false 解释:数组不能分割成两个元素和相等的子集。
代码(二维)
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0; //nums的元素之和
for(int i=0; i < nums.length; i++){
sum += nums[i];
}
//sum为奇数,绝对分不开
if(sum % 2 == 1){
return false;
}
//sum为偶数,其实就是在nums中找是否存在几个物品可以满足容量=sum/2的背包
int m = nums.length; //物品种类
int n = sum / 2; //背包容量
int[][] dp = new int[m][n+1]; //dp[i][j]表示从0-i中任取物品,能装在背包容量j的最大价值
//dp数组初始化
for(int i=0; i < m; i++){
dp[i][0] = 0; //第一列容量为0,价值全为0
}
for(int j=0; j <= n; j++){
//第一行,容量装不了物品0,价值为0
if(j - nums[0] < 0){
dp[0][j] = 0;
}
//容量能装物品0,价值为物品0价值
else{
dp[0][j] = nums[0]; //物品的价值就是数字大小
}
}
//遍历顺序:从上到下+从左到右(先物品后背包,其实交换也行)
for(int i=1; i < m; i++){
for(int j=1; j <= n; j++){
//防止j-nums[i]下标越界,容量j放不了物品i(只放物品i)
if(j - nums[i] < 0){
dp[i][j] = dp[i-1][j];
}
else{
int no = dp[i-1][j]; //不取物品i:当前j的容量不能再放物品i了
int yes = dp[i-1][j-nums[i]] + nums[i]; //取物品i:不取且容量减去物品i的价值+物品i的价值
dp[i][j] = Math.max(no, yes);
}
}
}
return dp[m-1][n] == n; //可以从集合中找到任意几个数,满足和=sum/2,返回true
}
}
代码(一维)
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0; //nums的元素之和
for(int i=0; i < nums.length; i++){
sum += nums[i];
}
//sum为奇数,绝对分不开
if(sum % 2 == 1){
return false;
}
//sum为偶数,其实就是在nums中找是否存在几个物品可以满足容量=sum/2的背包
int m = nums.length; //物品种类
int n = sum / 2; //背包容量
int[] dp = new int[n+1]; //dp[j]表示从0-i中任取物品,能装在背包容量j的最大价值
//一维dp数组初始化:默认全为0
//遍历顺序:物品正序,背包倒叙(先物品后背包,不能交换)
for(int i=0; i < m; i++){
//j >= nums[i]:防止下标越界,容量j放不了物品i(只放物品i)
for(int j=n; j >= nums[i]; j--){
int no = dp[j]; //不取物品i:当前j的容量不能再放物品i了
int yes = dp[j-nums[i]] + nums[i]; //取物品i:不取且容量减去物品i的价值+物品i的价值
dp[j] = Math.max(no, yes);
}
}
return dp[n] == n; //可以从集合中找到任意几个数,满足和=sum/2,返回true
}
}
1049、最后一块石头的重量II
题目
有一堆石头,用整数数组 stones
表示。其中 stones[i]
表示第 i
块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x
和 y
,且 x <= y
。那么粉碎的可能结果如下:
- 如果
x == y
,那么两块石头都会被完全粉碎; - 如果
x != y
,那么重量为x
的石头将会完全粉碎,而重量为y
的石头新重量为y-x
。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0
。
示例 1:
输入:stones = [2,7,4,1,8,1] 输出:1 解释: 组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1], 组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1], 组合 2 和 1,得到 1,所以数组转化为 [1,1,1], 组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
示例 2:
输入:stones = [31,26,33,21,40] 输出:5
代码(二维)
class Solution {
public int lastStoneWeightII(int[] stones) {
int m = stones.length; //物品种类
int sum = 0; //物品总重量
for(int i : stones){
sum += i;
}
//我们希望尽可能把石头分成重量接近的两堆,才能抵消最小
int n = sum / 2; //背包容量
//dp[i][j]表示容量为j时,从0-1个石头里任取,达到的最大重量
int[][] dp = new int[m][n+1];
//dp数组初始化
for(int i=0; i < m; i++){
dp[i][0] = 0; //第一列,背包容量为0
}
for(int j=0; j <= n; j++){
//第一行,能放下石头0的,dp价值就是石头0的重量
if(j - stones[0] < 0){
dp[0][j] = 0;
}
else{
dp[0][j] = stones[0];
}
}
//遍历顺序:从上到下+从左到右(先石头后重量,交换也行)
for(int i=1; i < m; i++){
for(int j=1; j <= n; j++){
//背包j单独装石头i都放不下,会下标越界
if(j - stones[i] < 0){
dp[i][j] = dp[i-1][j]; //按不取石头i处理
}
else{
int no = dp[i-1][j]; //不取:背包j取不了石头i了
int yes = dp[i-1][j-stones[i]] + stones[i]; //能取
dp[i][j] = Math.max(no, yes);
}
}
}
//遍历结束时,dp[m-1][n]表示容量为n时,任取石头的最大重量
return sum - dp[m-1][n] * 2; //最大分成dp[m-1][n]的两堆
}
}
代码(一维)
class Solution {
public int lastStoneWeightII(int[] stones) {
int m = stones.length; //物品种类
int sum = 0; //物品总重量
for(int i : stones){
sum += i;
}
//我们希望尽可能把石头分成重量接近的两堆,才能抵消最小
int n = sum / 2; //背包容量
//dp[i][j]表示容量为j时,从0-1个石头里任取,达到的最大重量
int[] dp = new int[n+1];
//dp一维数组初始化:默认全为0
//遍历顺序:先物品正序,后背包倒叙,不能交换
for(int i=0; i < m; i++){
//j >= stones[i]:背包j单独装石头i都放不下,会下标越界
for(int j=n; j >= stones[i]; j--){
int no = dp[j]; //不取:背包j取不了石头i了
int yes = dp[j-stones[i]] + stones[i]; //能取
dp[j] = Math.max(no, yes);
}
}
//遍历结束时,dp[n]表示容量为n时,任取石头的最大重量
return sum - dp[n] * 2; //最大分成dp[m-1][n]的两堆
}
}