
我在准备华为OD机考的时候遇到“快递投放问题”这道C卷真题用的是Java全程开着双机位监考。第一机位拍人脸和上半身第二机位在侧后方拍手和屏幕整个编码过程都会被录下来。说实话题目本身不复杂属于排序加贪心的经典套路但它把“怎么在限定载重下运输尽可能多的快递”这种工程问题考得很细稍不注意边界条件就可能掉分。这篇文章不打算讲虚的直接把题目的常见形态、解题思路、Java实现、机考环境下的注意事项一次说清楚尤其适合正在刷华为OD机试的同学参考。快递投放题是我备考时最早遇到的“看起来像背包、其实不是背包”的题目之一。很多同学一看“装载”“最大化数量”就条件反射地想动态规划结果凭空把难度拉高了。等我把题目的数学本质拆开你会发现它就是一个排序加贪心过程极其简洁。但简洁不代表容易过越简单的题目越容易在输入解析、超重过滤、整数溢出这些地方翻车。下面我按自己的备考顺序来写先理解题目在考什么再聊双机位环境下怎么准备然后讲清楚贪心策略为什么成立给出可运行的Java代码最后分享我整理的测试用例和备考经验。1. 快递投放问题放在C卷里到底在考什么1.1 常见题面货车载重与快递重量我刷到的“快递投放/运输”题大致是这样一辆运输快递的货车最大载重量为 W现有 n 个快递包裹重量分别为 weights[i]问一次最多能带上多少个包裹。每个包裹都是独立的不能拆分也没有价值差异只要总重量不超过 W 就可以一起运输。有的版本会写成“快递投放”比如把包裹装入货架或柜子但数学本质完全一样给定一组物品和容量上限求能装下的最大物品个数。这个题有几个容易混淆的点。一是包裹重量可能大于载重这种包裹理论上永远运不了直接剔除否则会影响后面的贪心统计。二是重量可以是 0 或负数吗现实里不可能但测试用例里偶尔会出现 00 重量的包裹不占载重算不算“能运输”要看题目定义稳妥的做法是忽略因为个数不变。三是载重上限可能很大累加所有可行包裹重量时需要留意 int 溢出Java 里用 long 最稳妥。我备考时把这道题的各种变体都试了一遍比如“求最大数量”和“求最小货车数量”是完全不同的方向。快递投放求最大数量贪心即可如果换成“把所有快递装到若干货车上求最少需要多少辆”那就是背包类问题难度会明显上升。所以拿到题目第一步先看清求什么是数量还是装载方案。这个判断失误后面做得再多也是白费。1.2 C卷难度定位基础扎实比炫技重要华为OD机考的卷次在不同批次有区别C卷给我的整体感受是偏“基础 边界”。它不会像算法竞赛那样出复杂的图论或网络流而是更倾向于把业务场景翻译成基础数据结构问题。快递投放就是一个很典型的例子包装成物流业务内核只是排序加贪心。正因为场景包装多很多同学反而容易在输入格式上消耗过多时间或者在考虑“是否要动态规划”上犹豫。我选择 Java 作答是因为日常开发主要用 Java对集合、Scanner、类型转换这些 API 更熟。C卷允许的环境里 Java 和 Python 都有如果非要给建议你平时用哪个语言顺手就用哪个不要在考试前临时换语言。换语言导致的基础 API 不熟比算法不会更致命。用 Java 的话Collections.sort、int[]、Scanner 这几个必须练到不用思考就能写出来的程度。我当时花了差不多一周把这一类的排序贪心题都过了一遍发现它们有共性场景换得再花哨核心永远是“排序之后按某个规则取”。快递投放是“按重量升序取”删掉超重的活动安排是“按结束时间升序取”跳过冲突的分发饼干是“按胃口和尺寸都排序后取”。把这些共性抓出来C卷里至少有三成题都能用同一套思维框架解决。2. 双机位监考下怎么准备才不会被环境拖累2.1 合规摆放双机位提前半天调试这是华为OD机考的硬性要求。我当时被要求第一机位电脑摄像头拍摄人脸正面和上半身第二机位手机或笔记本摄像头放在侧后方大约45度角保证可以看到手部动作、键盘和电脑屏幕。第二机位放太近会被判定环境不符放太远又看不清双手需要提前找个合适位置固定好最好用支架。我不建议在考前几分钟才临时调整设备。之前有一起备考的朋友因为第二机位的支架没提前调试考试开始时发现角度不对又不敢离开座位只能单手扶着手机答题非常影响状态。正确做法是考前一天完整走一遍流程检查麦克风、摄像头、网络用系统自带相机看看两个机位的画面角度确认没有遮挡。桌面只留电脑、电源、草稿纸和证件其他杂物全部清走。双机位的目的是保证考试的严肃性不是为了难为谁。任何试图利用第二机位盲区做小动作的想法都很危险。第一机位和第二机位画面都会被录制还有AI动作检测和人工复核任何低头、眼神偏移都可能被标记一旦判定作弊直接取消成绩甚至影响后续所有面试流程。没必要为一道保底题拿自己的诚信去冒险这是我最想提醒大家的一点。2.2 熟悉在线编辑器手脑一致OD机考的在线编辑器长得不一样但共同点是基本没有智能补全。写Java时最难受的是忘记import或者Scanner的nextInt和nextLine混用导致读不到预期数据。这不是算法问题是环境熟练度问题。解决办法就是平时练习时不要用IDE的自动补全强迫自己手写import java.util.*;手写完整类名。另一个习惯是多用Scanner不要自己写复杂解析。Scanner的nextInt会自动忽略换行和空格读取由空格或换行分隔的数字非常稳。只有数据规模特别大比如超过100万才考虑BufferedReader加StringTokenizer。诚实说快递投放这道题的数据规模到10万就已经很少见Scanner完全够用。担心性能就先从读入方式优化而不是盲目上复杂代码。在线编辑器提交后编译错误信息会直接返回。现场看到编译错误时不要慌先检查类名是否Main是否少import是否有中文字符。我在本地IDE从不写类名Main但提交OJ必须把类名改掉这个细节能救很多次命。有些考场编辑器还会限制粘贴所以平时一定要练“裸敲”代码也就是不用补全、不用快捷键靠手指记忆把代码敲出来。3. 贪心策略的推导与复杂度分析3.1 为什么优先装轻的快递就是最优解我们要装尽可能多的快递每件快递对结果的贡献完全一样都是“1个”区别只有重量。既然所有包裹“数量价值”相同那当然优先选择重量更小的包裹这样同样的载重可以容纳更多件数。更严谨地说假设某套方案里装了一个较重的包裹 a同时有一个更轻的包裹 b 没有装进去。我们用 b 替换 a 后总重量减小腾出来的载重还能再装其他包裹方案数量只会更多不会更少。因此从最轻的开始逐个累加直到下一件会超重得到的件数就是最大值。这个证明看起来简单但能说明为什么不需要动态规划。只有包裹的价值不同才需要考虑“用同样载重获得更大价值”这里所有包裹价值相同唯一目标就是数量所以贪心是成立的。如果有包裹重量超过了载重直接排除因为它在任何方案中都无法放入留着只会干扰统计。比如 weights {15, 2, 3}capacity 10如果不过滤15排序后是2、3、15累加到15时超重输出2如果过滤15排序后是2、3累加也是2。看起来结果一样但换个例子就有区别了。拿 weights {15, 1, 9}capacity 10 来说过滤后是1、9能装2件不过滤的话排序后是1、9、15累加时到9已经不超到15才超最后count还是2。好像影响不大再看 weights {15, 1, 2, 3}capacity 5不过滤排序是1、2、3、15累加1、2、3时已经超过5所以count2过滤后是1、2、3排序一样count2。其实很多情况下不过滤也能碰巧得到正确答案因为超重的包裹放在最后面根本轮不到它。但如果是负数或者0混在里面排序位置靠前就会严重干扰累加结果。所以过滤不是一个“可做可不做”的优化而是一个必要防御。3.2 时间复杂度和数据规模怎么估算排序用Arrays.sort或Collections.sort时间复杂度是O(n log n)。排序后只需要一次遍历累加遇到超重就停止所以总体复杂度是O(n log n)其中n是包裹数量。这个复杂度在华为OD机考的典型数据范围内完全没有压力10^5的输入跑起来是毫秒级。空间上如果用List 存过滤后的重量也就O(n)。担心频繁包装拆箱的开销可以直接用int[]数组排序用Arrays.sort(int[])。两者都可以但int[]更省内存。还有一点容易被忽略如果载重W非常巨大比如接近2^31-1我们累加时用int会溢出为负数循环判断sum weight W就失效了。改成long sum 0就能规避。这个bug很隐蔽不加说明很难发现我自己的代码曾经就是在这里翻车的。3.3 与背包、装箱问题的边界划分有同学一看到“装载”“最大化数量”就脑补成背包问题。背包问题的标准形式是每个物品有重量和价值求不超过容量时的最大价值而快递投放问题所有物品的价值都等于1所以价值因素消失了问题退化成“去掉最重的若干物品直到总重不超限”。这正是贪心的适用范围。如果题目再进一步变成“每个货车有最大载重给一堆包裹求最少用几辆车”那就变成装箱问题Bin Packing或背包DP的变体不是本文的简单贪心能解决的。考试时一旦发现题目和你熟悉的模型有细微差别先不要急着套模板把目标函数读清楚。目标函数是“数量最多”基本往贪心想目标函数是“能否全部放下”“最小空间”再考虑DP或搜索。3.4 什么时候别用贪心改用DP或搜索我整理了一个简单判断规则如果每件物品对答案的贡献相同只看数量优先考虑贪心如果每件物品有不同的价值、成本、收益需要权衡取舍就可能要DP。快递投放里每件包裹都算1个所以贪心但如果每件包裹还有优先级权重比如加急件算3分普通件算1分那就变成01背包的变体贪心不能保证最优。另一种情况是包裹之间有约束关系比如某些包裹必须放在同一个投放点或者投递顺序有先后。这种约束一旦出现排序贪心往往会失效得改用回溯、DFS或者状态压缩。C卷里约束复杂的大题不多但读题时心里要有这根弦别默认什么题都能排序解决。4. Java实现从Scanner到BufferedReader逐步优化4.1 最稳的AC模板Scanner List 排序直接上代码注释完整import java.util.*; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); int n sc.nextInt(); // 快递数量 int capacity sc.nextInt(); // 货车载重 ListInteger weights new ArrayList(); for (int i 0; i n; i) { int w sc.nextInt(); if (w 0 w capacity) { weights.add(w); // 过滤掉超重和非法重量 } } Collections.sort(weights); // 从小到大排序 long sum 0; int count 0; for (int w : weights) { if (sum w capacity) { break; } sum w; count; } System.out.println(count); sc.close(); } }关键点都在注释里了。第一行读入n和capacity如果题目格式是多组数据把整个逻辑包一层while(sc.hasNext())即可。过滤条件要不要包括w 0看题目对0重量的定义如果不希望把0计入数量过滤掉是安全的。long sum是防溢出尤其载重很大时很关键。这个版本是我最推荐的现场答案。原因很简单有List和Collections.sort代码可读性最好除非输入规模大到必须优化否则Scanner的解析速度完全够用。考试时最重要的是“写对”其次才是“写快”。不要为了追求极致的性能牺牲正确性这个道理在快递投放这一题上体现得很充分。4.2 数据量大时用BufferedReader替代ScannerScanner方便但底层是正则解析数据量大时确实慢。如果n到了10^6这种级别推荐用BufferedReader手动解析import java.io.*; import java.util.*; public class Main { public static void main(String[] args) throws IOException { BufferedReader br new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st new StringTokenizer(br.readLine()); int n Integer.parseInt(st.nextToken()); int capacity Integer.parseInt(st.nextToken()); int[] weights new int[n]; int idx 0; st new StringTokenizer(br.readLine()); for (int i 0; i n; i) { int w Integer.parseInt(st.nextToken()); if (w 0 w capacity) { weights[idx] w; } } int[] valid Arrays.copyOf(weights, idx); Arrays.sort(valid); long sum 0; int count 0; for (int w : valid) { if (sum w capacity) break; sum w; count; } System.out.println(count); br.close(); } }这里用int[]存储过滤后的重量避免List装箱开销用Arrays.copyOf把有效长度截断出来。实际写题时如果数据没有大到需要这种优化优先选择4.1的简单版本逻辑清晰出错概率低。“能用简单方案就不要上复杂方案”这是我刷题踩坑后的真实感受。4.3 编译和运行时的三个致命细节第一类名必须是Main。很多本地IDE默认类名是Main或Hello提交时如果类名和文件名不一致会直接CE编译错误。建议在本地新建Java文件时就把public class Main写好。第二不要写package语句在线评测系统不会认得你的包结构。第三读入之前最好判断一下sc.hasNext()防止输入为空时报NoSuchElementException。这三个细节都属于“非算法错误”但挂的人比算法错还多。另一个实战技巧写完代码后把“过滤超重包裹”这一步单独测试一遍。很多同学看到weights里有大于capacity的值直接丢进排序然后发现明明重量总和大于载重却输出n就是因为没过滤。加一个if判断成本极低但能避免整道题翻车。4.4 多组输入的while模板有些机考题目不只一组测试数据而是持续读到文件末尾。上面两段代码都假设只有一组输入遇到多组就会出错。解决方式很简单把核心逻辑包进while循环import java.util.*; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); while (sc.hasNext()) { int n sc.nextInt(); int capacity sc.nextInt(); ListInteger weights new ArrayList(); for (int i 0; i n; i) { int w sc.nextInt(); if (w 0 w capacity) { weights.add(w); } } Collections.sort(weights); long sum 0; int count 0; for (int w : weights) { if (sum w capacity) break; sum w; count; } System.out.println(count); } sc.close(); } }这种写法等于把单组输入的代码变成了“多组通用”。在线评测系统对输入的结尾一般用EOF判断while(sc.hasNext())正好适配。唯一需要注意的是如果每组输入之间有空行Scanner的nextInt也能自动跳过所以不用额外处理。这个模板建议提前背下来可以套用到所有需要循环读入的题目上。5. 测试用例与边界自查让代码一次过5.1 一组覆盖主要情况的用例编号输入(n capacity / weights)期望输出说明15 10 / 1 2 3 4 54123410刚好满载25 100 / 10 20 30 40 505全部可装35 3 / 1 2 3 4 52123再加任何一件都超重43 10 / 15 2 3215超重被过滤23554 0 / 0 1 2 30载重为0装不了任何正重量包裹65 6 / 3 3 3 3 32两个3已经等于670 100无包裹81 1 / 11单个包裹恰好等于载重表格里第5个用例比较关键。载重为0时所有正重量快递都该被过滤最后输出0如果有人在过滤条件里写w capacity而不是w capacity就会出错。第8个用例帮助检查“恰好等于”的边界包裹重量等于载重时应该能被装入判断条件是w capacity不是w capacity。这两个边界是考场上最容易出错的地方。5.2 容易漏掉的边界情况和应对方式n0第一行是“0 10”循环不执行weights为空输出0。但别忘记在while(sc.hasNext())模式下也要正常返回。capacityInteger.MAX_VALUE所有重量累加后long能安全存下但int可能溢出。这是使用long的直接理由。包裹重量为负数不现实但万一测试用例有w 0的过滤条件会把它排除如果题目允许负数但不是有效包裹这样处理最安全。重复重量比如3,3,3,3,3配载重6排序后依次累加到第3个时超重输出2。排序和累加天然支持重复值不用额外处理。恰好载重123410应当输出4不是3。累加判断要用“小于等于”场景即if (sum w capacity) break而不是严格小于。每条边界都值得单独脑跑一遍。说实话这些边界情况在实际工程项目里就是单元测试的用例清单面试官看的是你能否系统性地覆盖它们而不只是“能把主流程跑通”。5.3 现场自测的黄金三分钟进考场后写完第一版不要急着提交。我先花一分钟跑题目给的样例确认主流程没问题再花一分钟构造“恰好等于载重”“第一个就超重”“全部超重”三个边界用例在本地脑跑或写在草稿纸上推演最后一分钟检查一下类名、import、long类型。这三分种用得很值比反复提交试错省时间。如果发现输出不对优先怀疑三件事过滤条件写错、累加顺序不对、读取数量对不上。先打印weights看看读进来的值是否正常很多时候问题出在输入上而不是算法上。6. C卷Java冲刺刷题方向和现场心态6.1 优先掌握的基础题型清单C卷虽然没有官方题库但根据热门的华为OD机试面经排序贪心、双指针、滑动窗口、哈希表、栈和队列、二叉树遍历、简单动态规划出现频率很高。快递投放属于排序贪心类是最容易拿分的一类建议优先全部吃透。具体做法是每天刷3-5道真题或模拟题重点不是题量而是把每道题的边界条件都总结出来。Java方面除了Scanner还要熟练HashMap、HashSet、ArrayList、LinkedList、StringBuilder、Arrays.sort、Collections.reverse、Integer.parseInt这些常用API。在线编辑器没有代码提示写错一个方法名或参数类型可能浪费几分钟。可以参考我自己的做法考前把常用API的签名抄在一张纸上每天默写一遍坚持一两周后就形成肌肉记忆了。像快递投放这种题从读题到AC最好控制在15分钟以内因为我见过很多同学不是不会做而是把时间都耗在查API上。6.2 考场时间分配和心态调整机考时长一般在两三个小时题目数量因批次而异。拿到题目后我是先花5分钟读题和确认输入输出格式再花10分钟想清楚解法确认边界条件然后开始编码。编码过程中不要频繁修改大方向先把主流程跑通最后再补过滤条件。不要在一个用例上钻牛角尖如果某个边界始终不过先打印输出中间计算过程定位是哪一步出问题。双机位环境下摄像头会把你的表情和动作都记录下来。无论遇到什么情况都不要有试图遮挡画面、低头看手机等动作。我第一次考试时因为紧张习惯性低头想题被监考老师语音提醒了一次虽然没有影响成绩但心态明显波动。后来我养成了“抬头看屏幕思考低头只写草稿”的习惯从源头上避免被误判。这听起来像教条但考场上确实管用。6.3 一个很隐秘的Java陷阱不要依赖第三方库这个和本题无关但既然说Java顺便提醒一句。有的C卷题目会要求解析JSON格式输入比如快递列表带编号和重量。如果你在代码里引入第三方库比如Fastjson或Jackson很可能会因为没有依赖而编译失败。OD机考在线环境通常只提供JDK标准库没有第三方jar。遇到需要解析格式的数据优先用String.split、正则或手写状态机不要依赖外部库。我见过不止一个人在准备机考时背了一堆Jackson API实际考场根本用不上。类似地写代码时不要用Java 8以上才有的语法特性除非确认环境支持。为了保险我考场上写的是最朴素的Java代码没有lambda表达式没有var推断没有流式操作。不是说这些不能用而是在紧张状态下越朴素的语法越不容易踩版本兼容的坑。快递投放题用for-each和基本类型数组就完全够了。最后再说点个人体会。我刷快递投放这类简单题时最大的收获不是背会了某个模板而是养成了“先过滤非法条件再套算法”的习惯。很多题目看起来是算法不会做实际是读题和边界条件没处理好。双机位考试其实没那么可怕把设备调试好、把桌面清理干净、把心态放稳剩下的就是平时积累的编码手感。如果你也在准备华为OD机考C卷建议把这道快递投放题当作热身题认真把Java版代码跑到一次AC后面的排序贪心类题目都会顺手很多。