1011. 在 D 天内送达包裹的能力
传送带上的包裹必须在 D 天内从一个港口运送到另一个港口。
传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
返回能在 D 天内将传送带上的所有包裹送达的船的最低运载能力。
示例 1:
输入:weights = [1,2,3,4,5,6,7,8,9,10], D = 5
输出:15
解释:
船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
第 1 天:1, 2, 3, 4, 5
第 2 天:6, 7
第 3 天:8
第 4 天:9
第 5 天:10
请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。
示例 2:
输入:weights = [3,2,2,4,1,4], D = 3
输出:6
解释:
船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示:
第 1 天:3, 2
第 2 天:2, 4
第 3 天:1, 4
示例 3:
输入:weights = [1,2,3,1,1], D = 4
输出:3
解释:
第 1 天:1
第 2 天:2
第 3 天:3
第 4 天:1, 1
提示:
1 <= D <= weights.length <= 50000
1 <= weights[i] <= 500
/**
思路:
对于一艘承载力为K船来说,我们必然会在不超过其承载力的前提下贪心地往上装载货物,
这样才能使得运送包裹所花费的时间最短。
如果船在承载力为K的条件下可以完成在D天内送达包裹的任务,那么任何承载力大于K的条件下依然也能完成任务。
我们可以让这个承载力K从max(weights)开始(即所有包裹中质量最大包裹的重量,
低于这个重量我们不可能完成任务),逐渐增大承载力K,直到K可以让我们在D天内送达包裹。
此时K即为我们所要求的最低承载力。
逐渐增大承载力K的方法效率过低,让我们用二分查找的方法来优化它。
算法:
定义函数canShip(D,K)来判断在最低承载力为K的情形下能否在D天内送达所有包裹。
我们所要做的就是按照传送带上货物的顺序,依次且尽可能多地往船上装载货物,当该艘船无法装下更多货物时,我们换一搜船,同时将天数加111。当运输完所有货物后,我们判断所用的天数是否小于等于DDD。
用二分查找的方式,来查找这个最低承载力,如果mid可以完成任务,
我们把查找范围缩减至[lo,mid],注意不是mid+1,因为mid可能是我们所求的解),
否则我们去[mid+1,hi]区间中继续查找,详情见代码。
*/
public static int shipWithinDays(int[] weights, int D) {
int n=weights.length;
int sum=0,max=0;
for (int i = 0; i < n; i++){
sum+=weights[i];
max=max>weights[i]?max:weights[i];
}
int left=max,right=sum;
while (left<right){
int mid=left+(right-left)/2;
if (canShip(weights,D,mid)){ //判断在最低承载力为K的情形下能否在D天内送达所有包裹。
right=mid;
}else
left=mid+1;
}
return left;
}
private static boolean canShip(int[] weights, int D, int K) {
int cur=K;
for (int i = 0; i < weights.length; i++) {
if (weights[i]>K) return false;
if (cur<weights[i]){
cur=K;
D--;
}
cur-=weights[i];
}
return D>0; //能否在D天内送达包裹
}
public static void main(String[] args) {
int[] weights = {1,2,3,4,5,6,7,8,9,10};
System.out.println(shipWithinDays(weights, 5));
}
}
410. 分割数组的最大值
给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。
注意:
数组长度 n 满足以下条件:
1 ≤ n ≤ 1000
1 ≤ m ≤ min(50, n)
示例:
输入:
nums = [7,2,5,10,8]
m = 2
输出:
18
解释:
一共有四种方法将nums分割为2个子数组。
其中最好的方式是将其分为[7,2,5] 和 [10,8],
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。
public class Num410 {
public static int splitArray(int[] nums, int m) {
int n=nums.length;
int sum=0,max=0;
for (int i = 0; i < n; i++) {
sum+=nums[i];
max=max>nums[i]?max:nums[i];
}
int low=max,high=sum; //数组各自和的最大值:
//最小的可能性就是子数组只有一个数(该数组的最大值),
//最大的可能性就是子数组里有全部的数(数组和即为最大值)
while (low<high){
int mid=low+(high-low)/2; //子数组各自和的最大值为Mid
if (judge(nums,mid,m)){ //判断当最大值为Mid时,是否能分成m个子数组
high=mid;
}else
low=mid+1;
}
return low;
}
private static boolean judge(int[] nums, int K, int m) {
int cur=K;
for (int num : nums) {
if (cur<num){
m--;
cur=K;
}
cur-=num;
}
return m>0;
}
public static void main(String[] args) {
int[] nums = {7,2,5,10,8};
System.out.println(splitArray(nums, 2));
}
}