一、题目描述:
珂珂喜欢吃香蕉。这里有 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 <= piles.length <= 10^4 piles.length <= H <= 10^9 1 <= piles[i] <= 10^9
二、解题思路
二分搜索问题的泛化,四步法 :
- 确定变量x 一般就是要求的最小值或最大值
- 确定f(x) f是随着x的递增或递减的函数
- 确定target f(x)=target约束条件
- 确定left,right边界
2.1 递增和递减函数
递增函数:
递减函数:
2.2 解题
按步骤来:
2.2.1. 确定x
x就是 每小时吃香蕉的根数: x根/小时。
2.2.2 确定f(x)
看到target是小时,所以f(x)就是吃完所有香蕉的需要的总小时。
int f(int[] piles,x){
int hours=0;
for(int i=0;i<piles.length;i++){
hours+=piles[i]/x;
if(piles[i]%x>0){
hours++;
}
}
return hours;
}
2.2.3.确定约束条件
target==H。相当于找到等于target的元素。
2.2.4 左右边界确定
left左边界明显为1,有边界,看题目描述: 1 <= piles[i] <= 10^9,因此right取1000000000。当然,因为x表示吃香蕉的根数,它不会超过N堆香蕉的最大根数,所以可以遍历取得piles[i]中的最大值。
注意,f是递减的函数!!
public int minEatingSpeed(int[] piles, int H) {
int left = 1;
int right = 1000000000 + 1;
while (left ‹ right) {
int mid = left + (right - left) / 2;
if (f(piles, mid) == H) {
// 搜索左侧边界,则需要收缩右侧边界
right = mid;
} else if (f(piles, mid) ‹ H) {
// 需要让 f(x) 的返回值大一些
right = mid;
} else if (f(piles, mid) › H) {
// 需要让 f(x) 的返回值小一些
left = mid + 1;
}
}
return left;
}
最终, 因为要求x的最小值,那么其实就是求f(x)==target的左边第一个出现的元素!
所以代码如下:
class Solution {
public int minEatingSpeed(int[] piles, int h) {
int left=1;
int right=1000000000;
while(left<=right){
int mid=left+(right-left)/2;
int cost= eatCostTime(mid,piles);
if(cost> h){
//因为是递减函数,所以要让eatCostTime返回更小的值。因此调整的是左边界
left=mid+1;
}else if(cost< h){
//因为是递减函数,所以要让eatCostTime返回更大的值。因此调整的是左边界
right=mid-1;
}else{
System.out.println("mid: "+mid);
if(0==mid||eatCostTime(mid-1,piles)!=h){
return mid;
}else{
right=mid-1;
}
}
}
// 没有找到
return left;
}
public int eatCostTime(int x,int[] piles){
int hour=0;
for(int i=0;i<piles.length;i++){
hour+=piles[i]/x;
if(piles[i]%x>0){
hour++;
}
}
return hour;
}
}