代码随想录算法训练营第四十一天| 46. 携带研究材料(第六期模拟笔试)、46. 携带研究材料(第六期模拟笔试)空间优化法、416. 分割等和子集、1049. 最后一块石头的重量 II

[KamaCoder] 46. 携带研究材料(第六期模拟笔试)

[KamaCoder] 46. 携带研究材料(第六期模拟笔试)文章解释

[KamaCoder] 46. 携带研究材料(第六期模拟笔试)视频解释

题目描述:

​​​​​​​

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。 

小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。

输入描述

第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。

第二行包含 M 个正整数,代表每种研究材料的所占空间。 

第三行包含 M 个正整数,代表每种研究材料的价值。

输出描述

输出一个整数,代表小明能够携带的研究材料的最大价值。 

输入示例
6 1
2 2 3 1 5 2
2 3 1 5 4 3
输出示例
5
提示信息

小明能够携带 6 种研究材料,但是行李空间只有 1,而占用空间为 1 的研究材料价值为 5,所以最终答案输出 5。 

数据范围: 
1 <= N <= 5000 
1 <= M <= 5000 
研究材料占用空间和价值都小于等于 1000

[KamaCoder] 46. 携带研究材料(第六期模拟笔试) 

自己看到题目的第一想法

    理论基础直接看文章解释啦,这样效率高一些(最近被算法占用的时间确实太长了)。

看完代码随想录之后的想法

    嗯。。。很神奇!递归不是递归,贪心有点像,总是从小到大慢慢推算, 结果就算出预期的值了。

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int backpageCapacity = -1;
        int[] weights = null;
        int[] values = null;
        while (scanner.hasNextLine()) {
            String currentLine = scanner.nextLine();
            if (backpageCapacity == -1) {
                backpageCapacity = Integer.parseInt(currentLine.split(" ")[1]);
            } else if (weights == null) {
                String[] weightStr = currentLine.split(" ");
                weights = new int[weightStr.length];
                for (int i = 0; i < weights.length; i++) {
                    weights[i] = Integer.parseInt(weightStr[i]);
                }
            } else {
                String[] valueStr = currentLine.split(" ");
                values = new int[valueStr.length];
                for (int i = 0; i < values.length; i++) {
                    values[i] = Integer.parseInt(valueStr[i]);
                }
            }
        }
        // dp[i][j] 表示从 1 ~ i 个物品中选取 n 个,
        // 放入容量为 j 的背包, 所得到的最大价值
        int[][] dp = new int[weights.length][backpageCapacity + 1];
        // 递推公式:dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i])
        // 初始化:
        for (int i = backpageCapacity; i>= weights[0]; i--) {
            dp[0][i] = values[0];
        }
        for (int i = 0; i < weights.length; i++) {
            dp[i][0] = 0;
        }
        // 遍历顺序:
        //     先背包容量, 再物品顺序
        //     (先物品顺序, 再背包容量也是可以的,推荐)
        for (int i = 1; i <= backpageCapacity; i++) {
            for (int j = 1; j < weights.length; j++) {
                if (weights[j] > i) {
                    dp[j][i] = dp[j - 1][i];
                    continue;
                }
                dp[j][i] = Math.max(dp[j - 1][i], dp[j - 1][i - weights[j]] + values[j]);
            }
        }
        System.out.println(dp[weights.length - 1][backpageCapacity]);
    }
}
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int backpageCapacity = -1;
        int[] weights = null;
        int[] values = null;
        while (scanner.hasNextLine()) {
            String currentLine = scanner.nextLine();
            if (backpageCapacity == -1) {
                backpageCapacity = Integer.parseInt(currentLine.split(" ")[1]);
            } else if (weights == null) {
                String[] weightStr = currentLine.split(" ");
                weights = new int[weightStr.length];
                for (int i = 0; i < weights.length; i++) {
                    weights[i] = Integer.parseInt(weightStr[i]);
                }
            } else {
                String[] valueStr = currentLine.split(" ");
                values = new int[valueStr.length];
                for (int i = 0; i < values.length; i++) {
                    values[i] = Integer.parseInt(valueStr[i]);
                }
            }
        }
        // dp[i][j] 表示从 1 ~ i 个物品中选取 n 个,
        // 放入容量为 j 的背包, 所得到的最大价值
        int[][] dp = new int[weights.length][backpageCapacity + 1];
        // 递推公式:dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i])
        // 初始化:
        for (int i = backpageCapacity; i>= weights[0]; i--) {
            dp[0][i] = values[0];
        }
        for (int i = 0; i < weights.length; i++) {
            dp[i][0] = 0;
        }
        
        for (int i = 1; i < weights.length; i++) {
            for (int j = 1; j <= backpageCapacity; j++) {
                if (weights[i] > j) {
                    dp[i][j] = dp[i - 1][j];
                    continue;
                }
                dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i]);
            }
        }
        System.out.println(dp[weights.length - 1][backpageCapacity]);
    }
}

 

 自己实现过程中遇到哪些困难

    应该还是很多的,但是因为题目本身就挺难的, 最后下来因为一直在思考递推公式,已经几乎忘记过程中的困难了。。。

[KamaCoder] 46. 携带研究材料(第六期模拟笔试)

[KamaCoder] 46. 携带研究材料(第六期模拟笔试)文章解释

[KamaCoder] 46. 携带研究材料(第六期模拟笔试)视频解释

题目:

题目描述

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。 

小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。

输入描述

第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。

第二行包含 M 个正整数,代表每种研究材料的所占空间。 

第三行包含 M 个正整数,代表每种研究材料的价值。

输出描述

输出一个整数,代表小明能够携带的研究材料的最大价值。 

输入示例
6 1
2 2 3 1 5 2
2 3 1 5 4 3
输出示例
5
提示信息

小明能够携带 6 种研究材料,但是行李空间只有 1,而占用空间为 1 的研究材料价值为 5,所以最终答案输出 5。 

数据范围: 
1 <= N <= 5000 
1 <= M <= 5000 
研究材料占用空间和价值都小于等于 1000

[KamaCoder] 46. 携带研究材料(第六期模拟笔试)空间优化 

自己看到题目的第一想法

    类似计算斐波那契数列时候 dp 的复用么?因为当前物品是否可以放入背包, 都是依赖左上角的值, 所以只需要 dp[packageCapacity] 就够了。需要取 dp[i - 1][j] 时, 直接取 dp[i][j]就行,因为 dp 已经不是二维数组了, 这里 dp[i] 不存在, 因此退化成直接取  dp[j] = Math.max(dp[j], dp[j - weight[i]] + valuej[i]).

    但是真的这么写下来后,发现得到的值超级大T_T

看完代码随想录之后的想法

    原来因为 dp[] 数组复用后,低位的物品价值不断的被累加到高位, 导致背包计算出来的价值过大。

    这里的关键在于,遍历背包容量时候, 要从大到小遍历, 因为当前物品是否能放入背包, 都是依赖上一行的结果, 和本行的其他值并无关系, 因此从大到小遍历是没有问题的。

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int backpageCapacity = -1;
        int[] weights = null;
        int[] values = null;
        while (scanner.hasNextLine()) {
            String currentLine = scanner.nextLine();
            if (backpageCapacity == -1) {
                backpageCapacity = Integer.parseInt(currentLine.split(" ")[1]);
            } else if (weights == null) {
                String[] weightStr = currentLine.split(" ");
                weights = new int[weightStr.length];
                for (int i = 0; i < weights.length; i++) {
                    weights[i] = Integer.parseInt(weightStr[i]);
                }
            } else {
                String[] valueStr = currentLine.split(" ");
                values = new int[valueStr.length];
                for (int i = 0; i < values.length; i++) {
                    values[i] = Integer.parseInt(valueStr[i]);
                }
            }
        }
        // dp[i][j] 表示从 1 ~ i 个物品中选取 n 个,
        // 放入容量为 j 的背包, 所得到的最大价值
        int[] dp = new int[backpageCapacity + 1];
        // 递推公式:dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i])
        // 初始化:
        
        for (int i = 0; i < weights.length; i++) {
            for (int j = backpageCapacity; j >= weights[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);
            }
        }
        System.out.println(dp[backpageCapacity]);
    }
}

自己实现过程中遇到哪些困难

    当然就是背包遍历顺序啦~ 一开始用从小到大的顺序遍历, 正确的应该是从大到小遍历,同时对背包的初始化也有了优化的方案, 当背包容量小于当前物品重量时,当前背包容量以及更小的背包容量就不需要继续初始化了。 

[LeetCode] 416. 分割等和子集

[LeetCode] 416. 分割等和子集 文章解释

[LeetCode] 416. 分割等和子集 视频解释

题目:

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

[LeetCode] 416. 分割等和子集 

自己看到题目的第一想法

    无

看完代码随想录之后的想法

    等和子集就等于我有一个背包, 这个背包的容量为所有数相加起来的和的一半。要找到几个数,刚好能放入这个背包中(数字的大小即是物品的重量,也是物品的价值)。对于背包来说, 计算的是背包容量能装入的最大价值,而我们要找的是背包能装下的最大价值,是否等于背包的容量本身。

// 一维数组版本
class Solution {
    public boolean canPartition(int[] nums) {
        // sum 为 nums 的元素和, 分割等和子集就是要从一堆数字中找出几个数字, 和等于 sum/2
        // 可以转化为背包问题就是, 从一堆数字中挑选 n 个数字, 装进容量为 sum/2 的背包中
        // 看最后能不能最终给背包装满
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        if (sum%2 == 1) {
            return false;
        }
        sum = sum/2;
        // dp[i] 表示从下标为 0~j(j 为 nums 数组的索引,从 0 开始) 的元素中选数字, 放入容量为 i 的容器里,能放入的最大容量
        int[] dp = new int[sum + 1];
        // 递推公式: dp[i] = Math.max(dp[i], dp[i - nums[i]] + nums[i])
        // 初始化: 不需要额外初始化
        
        // 遍历顺序: 从左到右, 从上到下,第一排已经初始化过了,从第二排开始 
        for (int i = 0; i < nums.length; i++) {
            for (int j = sum; j >= nums[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        // 模拟计算 dp 值: 跳过 
        return dp[sum] == sum;
    }
}
// 二维数组版本
class Solution {
    public boolean canPartition(int[] nums) {
        // sum 为 nums 的元素和, 分割等和子集就是要从一堆数字中找出几个数字, 和等于 sum/2
        // 可以转化为背包问题就是, 从一堆数字中挑选 n 个数字, 装进容量为 sum/2 的背包中
        // 看最后能不能最终给背包装满
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        if (sum%2 == 1) {
            return false;
        }
        sum = sum/2;
        // dp[i] 表示从下标为 0~j(j 为 nums 数组的索引,从 0 开始) 的元素中选数字, 放入容量为 i 的容器里,能放入的最大容量
        int[] dp = new int[sum + 1];
        // 递推公式: dp[i] = Math.max(dp[i], dp[i - nums[i]] + nums[i])
        // 初始化: 不需要额外初始化
        
        // 遍历顺序: 从左到右, 从上到下,第一排已经初始化过了,从第二排开始 
        for (int i = 0; i < nums.length; i++) {
            for (int j = sum; j >= nums[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        // 模拟计算 dp 值: 跳过 
        return dp[sum] == sum;
    }
}

 

自己实现过程中遇到哪些困难

    要将数字和转换为背包的问题,对于 dp[j] 的值, 表示的是从 nums 中取 0 ~ i 个数的值,从这几个数中取几个数求和,最接近 j 的那个和的值。当 i 遍历到 nums.lenght - 1 之后,表示所有数字都遍历过了,而 dp[target] 表示在所有的数字中, 选取 n 个数字, 将这几个数字相加后,值不大于 target 的最大的和。 

    感觉还是挺绕的,如果不是放在动态规划章节,如果不是先看了题解。可能都想不到用动态规划的解法。

[LeetCode] 1049. 最后一块石头的重量 II
[LeetCode] 1049. 最后一块石头的重量 II 文章解释

[LeetCode] 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

提示:

  • 1 <= stones.length <= 30
  • 1 <= stones[i] <= 100 

[LeetCode] 1049. 最后一块石头的重量 II

自己看到题目的第一想法

    没想到没想到没想到!!!

看完代码随想录之后的想法

    对于背包问题,可以用另外一种思路来理解:

        如果要从一堆数字中, 挑出 n 个数, 使这 n 个数相加的结果是最接近于 sum, 则可以用背包算法。

    对于是石头的问题,就是要找到 n 个石头, 使这 n 个石头相加的结果,最接近于石头总重量的一半。

    于是乎,就可以套用背包公式了。这里的关键在于,能不能通过脑经急转弯把石头问题抽象成数字总和的背包逻辑呢!

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

自己实现过程中遇到哪些困难

    套用公式就没有什么难度了。 有一个 bug 是一开始 return 的时候写成 sum - dp[target],这是错的。 应该是 (sum - dp[target]) - dp[target] 。

  • 8
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方。例如,在第14训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方训练营还提供了每日的讨论知识点,例如在第15的讨论中,介绍了层序遍历的方和使用队列来模拟一层一层遍历的效果。在第16的讨论中,重点讨论了如何进行调试(debug)的方,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值