【OD机试题笔记】查找充电设备组合

题目描述

某个充电站,可提供 n 个充电设备,每个充电设备均有对应的输出功率。

任意个充电设备组合的输出功率总和,构成功率集合 P 的 1 个元素。

功率集合 P 的最优元素,表示最接近充电站最大输出功率 p_max 的元素。

输入描述
输入为 3 行:

第 1 行为充电设备个数 n
第 2 行为每个充电设备的输出功率
第 3 行为充电站最大输出功率 p_max
备注
充电设备个数 n > 0
最优元素必须小于或等于充电站最大输出功率 p_max
输出描述
功率集合 P 的最优元素

** 示例1
输入
4
50 20 20 60
90

输出
90

说明>
当充电设备输出功率50、20、20组合时,其输出功率总和为90,最接近充电站最大充电输出功率,因此最优元素为90。

** 示例2
输入
2
50 40
30

输出
0

说明>
所有充电设备的输出功率组合,均大于充电站最大充电输出功率30,此时最优元素值为0。

** 示例3
输入
3
2 3 10
9

输出
5

说明>
选择功率为2,3的设备构成功率集合,总功率为5,最接近最大功率9。不能选择设备10,因为已经超过了最大功率9。

** 示例4
输入
3
1 2 3
5

输出
5

思路

一、问题本质

这是01背包问题的经典变种(求不超过背包容量的最大价值),核心目标是从充电设备中选择任意个,使功率总和≤充电站最大输出功率p_max,且尽可能接近p_max

二、解法1:动态规划(迭代版,推荐)
  1. 状态定义dp[j]表示“最大允许功率为j时,能组合出的最大功率和”(即容量为j的背包可装下的最大“价值”,此处价值=功率);
  2. 初始化dp数组初始化为0(无设备时,所有功率限制下的最大和均为0);
  3. 状态转移
    • 遍历每个充电设备(共n个),对每个设备的功率w,从p_max倒序遍历到w(避免重复选择同一设备);
    • 转移方程:dp[j] = Math.max(dp[j], dp[j - w] + w)(选当前设备:dp[j-w]+w;不选:dp[j],取最大值);
  4. 结果输出dp[p_max]即为≤p_max的最大功率和(最优元素)。
三、解法2:记忆化搜索(递归版,辅助理解DP)
  1. 状态定义memo[i][j]表示“考虑前i个设备,最大允许功率为j时的最优功率和”,初始为-1(未计算);
  2. 递归边界i=0(无设备可选)时,返回0;
  3. 递归逻辑
    • 不选第i个设备:最优解=dfs(i-1, j)
    • 选第i个设备(仅当设备功率≤j):最优解=当前设备功率 + dfs(i-1, j-当前设备功率)
    • 取两者最大值,缓存结果到memo
  4. 结果输出dfs(n, p_max)即为最优解。
四、时空复杂度
解法时间复杂度空间复杂度
迭代DPO(n×p_max)O(p_max)(一维数组)
记忆化搜索O(n×p_max)O(n×p_max)(二维缓存)

代码

function solution() {
   const n = Number(readline());
   const list = readline().split(' ').map(Number);
   const p_max = Number(readline());
   
   const dp = Array(p_max+1).fill(0); // dp[j]表示容量为j的背包能装下的最大货物重量
   for (let i = 1; i <= n; i++) {
       for (let j = p_max; j >= list[i-1]; j--) {
           dp[j] = Math.max(dp[j-list[i-1]] + list[i-1], dp[j]);
       }
   }

   console.log(dp[p_max]);
}

function solution2() { // 记忆化搜索(帮助理解dp)
    const n = Number(readline());
    const powers = readline().split(' ').map(Number);
    const p_max = Number(readline());

    // 记忆化缓存:memo[i][j] 表示考虑前i个设备,最大允许功率为j时的最优解
    // -1 表示未计算过
    const memo = Array.from({ length: n }, () => Array(p_max + 1).fill(-1));

    // 递归函数:计算前i个设备(索引0~i-1)在最大允许功率为j时的最优功率和
    function dfs(i, j) {
        // 边界条件:没有设备可选时,最优解为0
        if (i === 0) return 0;

        // 若已计算过,直接返回缓存结果
        if (memo[i - 1][j] !== -1) return memo[i - 1][j];

        // 当前设备的功率
        const currentPower = powers[i - 1];

        // 选项1:不选第i个设备,最优解等于前i-1个设备在j限制下的解
        let maxSum = dfs(i - 1, j);

        // 选项2:若当前设备功率不超过j,可选择该设备,最优解为当前功率+前i-1个设备在j-currentPower限制下的解
        if (currentPower <= j) {
            maxSum = Math.max(maxSum, currentPower + dfs(i - 1, j - currentPower));
        }

        // 缓存结果并返回
        memo[i - 1][j] = maxSum;
        return maxSum;
    }

    // 计算前n个设备在p_max限制下的最优解
    const result = dfs(n, p_max);
    console.log(result);
}

// 测试用例
const cases = [
`4
50 20 20 60
90`,
`2
50 40
30`,
`3
2 3 10
9`,
`3
1 2 3
5`    
];

let caseIndex = 0, lineIndex = 0;
const readline = (() => {
    let lines = [];
    return () => {
        if (!lineIndex) lines = cases[caseIndex].trim().split('\n').map(l => l.trim());
        return lines[lineIndex++];
    };
})();

cases.forEach((_, i) => {
    caseIndex = i;
    lineIndex = 0;
    solution();
    console.log('-------');
});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值