题目难度: 中等
原题链接
今天继续更新 Leetcode 的剑指 Offer(专项突击版)系列, 大家在公众号 算法精选 里回复
剑指offer2
就能看到该系列当前连载的所有文章了, 记得关注哦~
题目描述
狒狒喜欢吃香蕉。这里有 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
题目思考
- 如何优化时间复杂度?
解决方案
思路
- 分析题目, 最容易想到的思路就是从速度 1 开始遍历, 基于当前速度计算吃完所有香蕉的时间, 如果小于 h 就直接返回
- 不过这样时间复杂度达到了
O(NM)
, 其中 N 是香蕉堆数, M 是速度上限, 即所有堆的最大香蕉根数 (没必要使用更大的速度, 因为每次只能吃一堆) - 根据题目规模, 这种做法肯定会超时, 如何优化呢?
- 不难发现, 当速度增大时, 吃完香蕉的时间一定是递减的, 这里存在单调性
- 我们可以利用这一点, 采用二分查找的方式加速
- 也就是基于速度上下限 1 和 M, 二分查找满足要求的最小速度: 如果当前速度不满足要求, 则向右半区间查找; 否则更新最终结果为较小值, 并向左半区间查找
- 而判断某个速度是否满足要求也很简单: 直接遍历所有堆, 累加每一堆按照当前速度吃完所需的时间, 如果时间总和不超过 h, 则满足要求, 否则不满足要求
- 最后, 遍历完整个区间后的最终结果即为满足要求的最小速度
- 下面代码中有详细的注释, 方便大家理解
复杂度
- 时间复杂度 O(NlogM): N 是香蕉的堆数, M 是所有堆的最大香蕉根数, 需要在区间
[1,M]
之间进行二分查找 (O(logM)), 而判断当前速度是否有效, 需要遍历整个堆 (O(N)), 所以整体时间复杂度就是 O(NlogM) - 空间复杂度 O(1): 只使用了几个常数空间的变量
代码
class Solution:def minEatingSpeed(self, piles: List[int], h: int) -> int:# 二分查找+有效性判断# 最小速度是1, 最大速度是所有堆的最大香蕉根数# 没必要使用更大的速度, 因为每次只能吃一堆s, e = 1, max(piles)res = edef isValid(speed):# 判断使用当前速度吃香蕉时, 能否在h小时内吃完hour = 0for p in piles:# 当前堆需要吃ceil(p/speed)小时hour += math.ceil(p / speed)if hour > h:# 总时间超过了h, 无效return False# 可以在h小时内吃完return True# 二分查找满足要求的最小速度while s <= e:m = (s + e) >> 1if isValid(m):# 当前速度有效, 更新res为两者较小值res = min(res, m)# 然后向左查找, 检查是否存在更小的有效速度e = m - 1else:# 当前速度无效, 只能向右查找s = m + 1return res
大家可以在下面这些地方找到我~😊
我的 GitHub
我的 Leetcode
我的 CSDN
我的知乎专栏
我的头条号
我的牛客网博客
我的公众号: 算法精选, 欢迎大家扫码关注~😊