第五章 - 高频考题(中等)
1906. 查询差绝对值的最小值
0875. 爱吃香蕉的珂珂

题目地址(875. 爱吃香蕉的珂珂)

题目描述

1
珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。
2
3
珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。
4
5
珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
6
7
返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。
8
9
10
11
示例 1:
12
13
输入: piles = [3,6,7,11], H = 8
14
输出: 4
15
示例 2:
16
17
输入: piles = [30,11,23,4,20], H = 5
18
输出: 30
19
示例 3:
20
21
输入: piles = [30,11,23,4,20], H = 6
22
输出: 23
23
24
25
提示:
26
27
1 <= piles.length <= 10^4
28
piles.length <= H <= 10^9
29
1 <= piles[i] <= 10^9
Copied!

前置知识

公司

  • 字节

思路

符合直觉的做法是,选择最大的堆的香蕉数,然后试一下能不能行,如果不行则直接返回上次计算的结果,如果行,我们减少 1 个香蕉,试试行不行,依次类推。计算出刚好不行的即可。这种解法的时间复杂度比较高,为 $O(N * M)$,其中 N 为 piles 长度, M 为 Piles 中最大的数。。
这道题如果能看出来是二分法解决,那么其实很简单。为什么它是二分问题呢?我这里画了个图,我相信你看了就明白了。
香蕉堆的香蕉个数上限是 10^9, 珂珂这也太能吃了吧?

关键点解析

  • 二分查找模板

代码

代码支持:Python,JavaScript
Python Code:
1
class Solution:
2
def solve(self, piles, k):
3
def possible(mid):
4
t = 0
5
for pile in piles:
6
t += (pile + mid - 1) // mid
7
return t <= k
8
9
l, r = 1, max(piles)
10
11
while l <= r:
12
mid = (l + r) // 2
13
if possible(mid):
14
r = mid - 1
15
else:
16
l = mid + 1
17
return l
Copied!
JavaScript Code:
1
function canEatAllBananas(piles, H, mid) {
2
let h = 0;
3
for (let pile of piles) {
4
h += Math.ceil(pile / mid);
5
}
6
7
return h <= H;
8
}
9
/**
10
* @param {number[]} piles
11
* @param {number} H
12
* @return {number}
13
*/
14
var minEatingSpeed = function (piles, H) {
15
let lo = 1,
16
hi = Math.max(...piles);
17
// [l, r) , 左闭右开的好处是如果能找到,那么返回 l 和 r 都是一样的,因为最终 l 等于 r。
18
while (lo <= hi) {
19
let mid = lo + ((hi - lo) >> 1);
20
if (canEatAllBananas(piles, H, mid)) {
21
hi = mid - 1;
22
} else {
23
lo = mid + 1;
24
}
25
}
26
27
return lo; // 不能选择hi
28
};
Copied!
复杂度分析
  • 时间复杂度:$O(max(N, N * logM))$,其中 N 为 piles 长度, M 为 Piles 中最大的数。
  • 空间复杂度:$O(1)$

模板

分享几个常用的的二分法模板。

查找一个数

1
public int binarySearch(int[] nums, int target) {
2
// 左右都闭合的区间 [l, r]
3
int left = 0;
4
int right = nums.length - 1;
5
6
while(left <= right) {
7
int mid = left + (right - left) / 2;
8
if(nums[mid] == target)
9
return mid;
10
else if (nums[mid] < target)
11
// 搜索区间变为 [mid+1, right]
12
left = mid + 1;
13
else if (nums[mid] > target)
14
// 搜索区间变为 [left, mid - 1]
15
right = mid - 1;
16
}
17
return -1;
18
}
Copied!

寻找最左边的满足条件的值

1
public int binarySearchLeft(int[] nums, int target) {
2
// 搜索区间为 [left, right]
3
int left = 0;
4
int right = nums.length - 1;
5
while (left <= right) {
6
int mid = left + (right - left) / 2;
7
if (nums[mid] < target) {
8
// 搜索区间变为 [mid+1, right]
9
left = mid + 1;
10
} else if (nums[mid] > target) {
11
// 搜索区间变为 [left, mid-1]
12
right = mid - 1;
13
} else if (nums[mid] == target) {
14
// 收缩右边界
15
right = mid - 1;
16
}
17
}
18
// 检查是否越界
19
if (left >= nums.length || nums[left] != target)
20
return -1;
21
return left;
22
}
Copied!

寻找最右边的满足条件的值

1
public int binarySearchRight(int[] nums, int target) {
2
// 搜索区间为 [left, right]
3
int left = 0
4
int right = nums.length - 1;
5
while (left <= right) {
6
int mid = left + (right - left) / 2;
7
if (nums[mid] < target) {
8
// 搜索区间变为 [mid+1, right]
9
left = mid + 1;
10
} else if (nums[mid] > target) {
11
// 搜索区间变为 [left, mid-1]
12
right = mid - 1;
13
} else if (nums[mid] == target) {
14
// 收缩左边界
15
left = mid + 1;
16
}
17
}
18
// 检查是否越界
19
if (right < 0 || nums[right] != target)
20
return -1;
21
return right;
22
}
Copied!
如果题目重点不是二分,也就是说二分只是众多步骤中的一步,大家也可以直接调用语言的 API,比如 Python 的 bisect 模块。
更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。
关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。