LeetCode 594题‘磁带利用率’详解:从背包DP到贪心交换,附C++完整代码与三大易错点

张开发
2026/4/20 5:58:40 15 分钟阅读

分享文章

LeetCode 594题‘磁带利用率’详解:从背包DP到贪心交换,附C++完整代码与三大易错点
LeetCode 594题‘磁带利用率’深度解析从动态规划到贪心策略的实战指南当你面对LeetCode 594这道关于磁带利用率的问题时是否曾困惑于如何将实际问题转化为算法模型这道题表面看似简单实则暗藏多个陷阱需要我们对背包问题和贪心算法有深入理解才能完美解决。本文将带你从题目本质出发逐步拆解两种核心解法并揭示那些容易导致WAWrong Answer的关键细节。1. 题目理解与建模题目描述给定n个程序及其占用的磁带长度a[i]以及磁带的总容量m。要求选择尽可能多的程序装入磁带且在程序数量相同的情况下使磁带利用率最高即已用空间尽可能大。关键建模点这是一个典型的双目标优化问题首要目标是最大化程序数量次要目标是最大化磁带利用率可以转化为0-1背包问题的变种每个程序相当于物品磁带容量是背包容量与传统背包不同我们需要同时跟踪两个指标程序数量和已用空间注意题目中的磁带利用率实际包含两个维度——程序数量和空间利用率这导致状态转移时需要特殊处理2. 动态规划解法详解2.1 DP状态设计我们需要设计一个能同时记录程序数量和空间利用率的状态结构struct Node { int count; // 存储的程序数量 int sum; // 占用的磁带长度 vectorint path; // 存储的程序路径可选 }; Node dp[MAX_CAPACITY]; // dp数组状态转移核心逻辑当考虑程序i时对于每个可能容量j从m到a[i]递减比较加入程序i后的状态与当前状态优先比较程序数量数量多的更优数量相同时选择sum更大的方案2.2 完整DP代码实现#include bits/stdc.h using namespace std; const int N 1e4 5; struct Node { int count 0; int sum 0; vectorint path; }; int main() { int n, m; cin n m; vectorint a(n); for (int i 0; i n; i) cin a[i]; vectorNode dp(m 1); // 逆序处理程序 for (int i n - 1; i 0; i--) { // 逆序处理容量 for (int j m; j a[i]; j--) { int new_count dp[j - a[i]].count 1; int new_sum dp[j - a[i]].sum a[i]; // 状态转移条件 if (new_count dp[j].count || (new_count dp[j].count new_sum dp[j].sum)) { dp[j].count new_count; dp[j].sum new_sum; dp[j].path dp[j - a[i]].path; dp[j].path.push_back(a[i]); } } } cout dp[m].count dp[m].sum endl; // 输出路径如果需要 for (int i dp[m].path.size() - 1; i 0; i--) { cout dp[m].path[i] ; } return 0; }2.3 三大易错点解析遍历顺序错误必须采用逆序处理容量j从m到a[i]错误示例for(int j a[i]; j m; j)会导致程序被重复选择状态转移条件不完整必须同时考虑count和sum两个维度常见错误只比较count或只比较sum等于情况的处理当count相同时必须正确处理sum的比较错误示例if(new_count dp[j].count new_sum dp[j].sum)漏掉了等于情况3. 贪心算法优化解法当程序总大小不超过磁带容量时直接选择所有程序即可。否则可以采用贪心策略初始选择将程序按大小升序排列尽可能多地选择小程序交换优化尝试用未选中的小程序替换已选中的大程序提高利用率3.1 贪心算法实现步骤vectorint solveGreedy(vectorint a, int m) { sort(a.begin(), a.end()); int total accumulate(a.begin(), a.end(), 0); // 情况1全部程序可以装入 if (total m) return { (int)a.size(), total }; // 情况2需要选择部分程序 int sum 0, count 0; vectorbool selected(a.size(), false); // 优先选择小程序 for (int i 0; i a.size(); i) { if (sum a[i] m) { sum a[i]; count; selected[i] true; } else { break; } } // 尝试交换优化 int left 0, right a.size() - 1; while (left count right count) { int diff a[right] - a[left]; if (sum diff m) { sum diff; selected[left] false; selected[right] true; left; right--; } else { break; } } // 统计最终结果 int final_sum 0, final_count 0; vectorint result; for (int i 0; i a.size(); i) { if (selected[i]) { final_sum a[i]; final_count; result.push_back(a[i]); } } return { final_count, final_sum }; }3.2 贪心与DP的对比分析特性动态规划解法贪心解法时间复杂度O(n*m)O(nlogn)空间复杂度O(m)O(n)最优解保证总能得到最优解可能不是最优解适用场景通用场景程序大小差异明显时效果好实现难度中等相对简单4. 实战调试技巧与面试要点4.1 常见调试场景边界条件测试磁带容量为0所有程序大小相同单个程序大小等于磁带容量特殊数据测试# 测试用例1所有程序都能装入 n 3, m 10, a [2, 3, 4] # 应输出3 9 # 测试用例2需要选择 n 4, m 10, a [2, 3, 4, 6] # 应输出3 9 # 测试用例3临界情况 n 5, m 15, a [1, 2, 3, 4, 5] # 应输出5 154.2 面试应答策略当面试官提出这道题时可以按照以下逻辑展开问题分析明确双目标特性数量优先利用率次之识别背包问题的特征解法选择首先提出DP解法解释状态设计然后讨论贪心优化的可能性复杂度分析DP解法的时间空间复杂度贪心解法的优势与局限性边界讨论容量为0的情况处理所有程序大小相同的情况在实际编码面试中建议优先实现DP解法因为它能保证正确性。如果时间允许可以补充讨论贪心优化的思路。

更多文章