LeetCode Day44

223 阅读5分钟

46. 携带研究材料(第六期模拟笔试)(不在力扣上)

题目描述

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。 小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。

输入

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

输出

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

思路

这个问题是典型的 0-1 背包问题,可以使用动态规划来解决。

第一步:确定dp数组和下标的含义

定义一个一维数组 dp,其中 dp[i] 表示小明在行李箱容量为 i 的情况下能携带的研究材料的最大价值。

第二步:确定递推公式

对于每种研究材料 m(占据空间为 space[m],价值为 value[m]),小明有两种选择:带或不带。

  • 如果不带,则 dp[i] 不变。
  • 如果带,则 dp[i] 可能会更新为 dp[i - space[m]] + value[m]

因此,递推公式为:

第三步:dp数组如何初始化

行李箱容量为 0 时,能携带的研究材料价值为 0,所以 dp[0] = 0。由于我们考虑不带任何物品的情况,所以其他 dp[i] 初始值也应为 0。

第四步:确定遍历顺序

首先遍历所有的研究材料,然后对每种材料,从大到小遍历 dp[i],以确保每种研究材料只被考虑一次。

第五步:举例推导dp数组

假设小明的行李空间为 1,有 6 种研究材料,空间分别为 [2, 2, 3, 1, 5, 2],价值分别为 [2, 3, 1, 5, 4, 3]

初始化 dp = [0, 0](长度为小明行李空间 + 1)。

遍历所有研究材料:

  • 第一种材料(空间 2,价值 2):由于空间大于行李空间,跳过。
  • 第二种材料(空间 2,价值 3):同上,跳过。
  • 第三种材料(空间 3,价值 1):同上,跳过。
  • 第四种材料(空间 1,价值 5):dp[1] = max(dp[1], dp[1-1] + 5) = max(0, 0 + 5) = 5
  • 第五种材料(空间 5,价值 4):同上,跳过。
  • 第六种材料(空间 2,价值 3):同上,跳过。

最终结果是 dp[1] = 5

题解

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    int M, N;
    std::cin >> M >> N;  // 输入研究材料的种类和小明的行李空间

    std::vector<int> spaces(M), values(M);
    for (int i = 0; i < M; ++i) {
        std::cin >> spaces[i];  // 输入每种研究材料的所占空间
    }
    for (int i = 0; i < M; ++i) {
        std::cin >> values[i];  // 输入每种研究材料的价值
    }

    std::vector<int> dp(N + 1, 0);  // 初始化动态规划数组

    // 动态规划计算
    for (int m = 0; m < M; ++m) {
        for (int i = N; i >= spaces[m]; --i) {
            dp[i] = std::max(dp[i], dp[i - spaces[m]] + values[m]);
        }
    }

    std::cout << dp[N] << std::endl;  // 输出小明能够携带的研究材料的最大价值

    return 0;
}

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

思路

这个问题可以转换为一个标准的 0-1 背包问题。等同于寻找一个子集,使其元素之和等于整个数组元素之和的一半。

第一步:确定dp数组和下标的含义

定义一个一维数组 dp,其中 dp[j] 表示是否存在一个子集,使得该子集的元素之和为 j

第二步:确定递推公式

对于每个数字 num,我们有两个选择:加入子集或不加入。

  • 如果不加入,则 dp[j] 保持不变。
  • 如果加入,则 dp[j] 可能会变成 true,即 dp[j] = dp[j] || dp[j - num]

第三步:dp数组如何初始化

dp[0] 应该初始化为 true,表示存在一个空子集,其和为 0。其他 dp[j] 应该初始化为 false

第四步:确定遍历顺序

首先遍历所有数字,然后对每个数字,从目标值向 0 遍历 dp[j]

第五步:举例推导dp数组

假设 nums = [1, 5, 11, 5],则 sum(nums) = 22,目标值 target = sum(nums) / 2 = 11

初始化 dp = [true, false, ..., false](长度为 target + 1)。

  • 第一个数字(1):dp[11] = dp[11] || dp[10] = false || false = false
  • 第二个数字(5):dp[11] = dp[11] || dp[6] = false || false = false
  • 第三个数字(11):dp[11] = dp[11] || dp[0] = false || true = true
  • 第四个数字(5):dp[11] 已经是 true,所以不需要改变。

最终结果是 dp[11] = true

题解

class Solution {
public:
    bool canPartition(std::vector<int>& nums) {
        int sum = std::accumulate(nums.begin(), nums.end(), 0);
        if (sum % 2 != 0) return false;
        
        int target = sum / 2;
        std::vector<bool> dp(target + 1, false);
        dp[0] = true;
        
        for (int num : nums) {
            for (int j = target; j >= num; --j) {
                dp[j] = dp[j] || dp[j - num];
            }
        }
        
        return dp[target];
    }
};