珂珂喜欢吃香蕉。这里有 n
堆香蕉,第 i
堆中有 piles[i]
根香蕉。警卫已经离开了,将在 h
小时后回来。
珂珂可以决定她吃香蕉的速度 k
(单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 k
根。如果这堆香蕉少于 k
根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。
珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
返回她可以在 h
小时内吃掉所有香蕉的最小速度 k
(k
为整数)。
示例 1:
输入:piles = [3,6,7,11], h = 8 输出:4
示例 2:
输入:piles = [30,11,23,4,20], h = 5 输出:30
示例 3:
输入:piles = [30,11,23,4,20], h = 6 输出:23
查找的是最小速度。由于题目限制了珂珂一个小时之内只能选择一堆香蕉吃,因此速度最大值就是这几堆香蕉中,数量最多的那一堆。速度的最小值是 1,还因为珂珂一个小时之内只能选择一堆香蕉吃,因此:「每堆香蕉吃完的耗时 = 这堆香蕉的数量 / 珂珂一小时吃香蕉的数量」,需要上取整。
假设吃香蕉的速度是 speed,则当一堆香蕉的个数是 pile时,吃掉这堆香蕉需要 pile / speed小时(上取整),由此可以计算出吃掉所有香蕉需要的时间。
如果在速度 speed 下可以在 h 小时内吃掉所有香蕉,则最小速度一定小于或等于 speed,因此将上界调整为 speed;否则,最小速度一定大于 speed,因此将下界调整为 speed+1。
解法1:
class Solution {
public int minEatingSpeed(int[] piles, int h) {
int max = 0;
for (int pile: piles) {
max = Math.max(max, pile);
}
int left = 1;
int right = max;
while (left < right) {
int speed = left + (right - left) / 2;
if (getHour(piles, speed) <= h) {
right = speed;
} else {
left = speed + 1;
}
}
return left;
}
private int getHour(int[] piles, int speed) {
int hour = 0;
for (int pile: piles) {
// 向上取整
hour += (pile + speed - 1) / speed;
}
return hour;
}
}
解法2:
class Solution {
public int minEatingSpeed(int[] piles, int h) {
int max = 0;
for (int pile: piles) {
max = Math.max(max, pile);
}
//闭区间写法
int left = 1;
int right = max;
while (left <= right) {
// 循环不变量:
// check(right + 1), true
// check(left - 1), false
int mid = left + (right - left) / 2;
if (check(piles, mid, h)) {
right = mid - 1;
} else {
left = mid + 1;
}
}
// left == right + 1
return left;
//左闭右开区间写法
// int left = 1;
// int right = max + 1;
// while (left < right) {
// // 循环不变量:
// // check(right), true
// // check(left - 1), false
// int mid = left + (right - left) / 2;
// if (check(piles, mid, h)) {
// right = mid;
// } else {
// left = mid + 1;
// }
// }
// return right;
//左开右闭区间写法
// int left = 0;
// int right = max;
// while (left < right) {
// // 循环不变量:
// // check(right + 1), true
// // check(left), false
// int mid = left + (right - left + 1) / 2;
// if (check(piles, mid, h)) {
// right = mid - 1;
// } else {
// left = mid;
// }
// }
// return right + 1;
//开区间写法
// int left = 0;
// int right = max + 1;
// while (left + 1 < right) {
// // 循环不变量:
// // check(right), true
// // check(left), false
// int mid = left + (right - left) / 2;
// if (check(piles, mid, h)) {
// right = mid;
// } else {
// left = mid;
// }
// }
// return right;
}
private boolean check(int[] piles, int x, int h) {
// 防止整型溢出
long hour = 0;
for (int pile: piles) {
// pile / x 向上取整
hour += (pile + x - 1) / x;
}
return hour <= h;
}
}
复杂度分析
时间复杂度:O(nlogm),其中 n 是数组 piles 的长度,m 是数组 piles 中的最大值。需要 O(n)的时间遍历数组找到最大值 m,二分查找需要执行 O(logm) 轮,每一轮二分查找需要 O(n) 的时间,因此总时间复杂度是 O(nlogm)。
空间复杂度:O(1)。