该文源自便利蜂一面时的算法题,当时没能给出比较优雅的解法。
事后复盘,并花时间对该题重新作答,在此记录下来。
一、算法题目
每天夜里便利蜂司机需要从仓库把装箱的包裹配送到门店。包裹重量不一,【必须按照出仓顺序】装车。
司机每跑一趟就能赚一趟的运费,但是最多不能超过要求的 N 趟, 请帮司机选择一辆【最低运载能力】的卡车来赚到更多运费。
例如:
顺序出仓的10个包裹重量依次为 [1,2,3,4,5,6,7,8,9,10] (单位吨), 需要在 5 趟内完成配送。 则需要选择一辆 15 吨的卡车能够刚好跑满 5 趟,赚到 5 份运费。
控制台输入格式: 1,2,3,4,5,6,7,8,9,10#5
输出:15
注意:趟数必须小于等于包裹个数,不能空车跑。
二、最初思路
假设每车刚好装满,则单车载重 = 总重 / 目标趟数,但包裹重量不一且顺序装车,故刚好装满概率极低,需要以单车均重为下限,朝上进行逼近。当时没想到优雅的逼近思路,故采用单车载重逐次+1的暴力逼近。
思路不佳,就不贴实现了。。。
三、复盘思路
a)题目要素
输入:
包裹重量数组;
条件:
装车顺序确定;
最大趟数确定;
输出:
满足要求的单车最低载重;
b)载重区间
单车载重区间的左边界应为 max(均重,最大重量),由于最大重量 >= 均重,故左边界直接取最大重量即可,右边界应为 sum(包裹重量)。
故得 最大重量 <= 单车载重 < 总重,左闭右开区间。
c)逼近策略
有了上下限,则可用二分法进行逼近。
每次取载重区间中点判定趟数是否满足要求:
- 若判定成功,则舍弃右区间,在左区间继续逼近;
- 若判定失败,则舍弃左区间,在右区间继续逼近;
d)趟数判定
按包裹顺序遍历,累加当车总重,若当车总重 > 单车负载,则该包裹应放到下一车,即当车总重 = 当前包裹重量,趟数 + 1。
- 若超过最大趟数,判定为失败;
- 否则,判定成功;
e)非法检查
职责上来说,该算法聚焦计算最低运载能力即可,输入的合法性检查应交由调用方。
四、实现代码
public int minCarLoad(int[] weights, int n){
// 载重区间:[最大重量, 总重量)
int min = 0, max = 0;
for (int weight : weights){
min = Math.max(weight, min);
max += weight;
}
while (min < max){
int mid = min + max >> 1;
// 当前趟次,该趟总重
int times = 1, sum = 0;
boolean flag = true;
// 趟数判定
for (int weight : weights){
if ((sum += weight) > mid){
if (++times > n){
flag = false;
break;
}
sum = weight;
}
}
// 二分逼近
if (flag){
max = mid;
} else {
min = mid + 1;
}
}
return max;
}