题目描述
原题链接: LeetCode.875 爱吃香蕉的珂珂
解题思路
- 如果当前堆剩余香蕉数量小于每小时吃的数量, 吃完当前堆就会休息不会去吃下一堆的香蕉, 所以吃完一堆所需时间就是堆的香蕉数量除以速度的向上取整值:\(\lceil {piles[i]/speed} \rceil\);
- 首先确定答案所处的范围, 速度最小值可以是1, 最大值很明显没必要超过所有堆中香蕉数量的最大值;
- 如果假定的速度speed能在不超过指定H小时内吃完所有香蕉, 任意大于speed的速度也肯定可以在H小时内吃完, 否则就该尝试更小的speed能否在指定时间内吃完;
- 二分判断函数逻辑就是给定speed能否在不超过H小时内吃完所有香蕉;
- 在给定范围内不断二分尝试, 找到最小符合需求的速度即可;
- 时间复杂度很明显就是O(nlogMAX);
- 对于二分范围, 可以粗略定一个较大的, 对复杂度不会有多大影响, 追求极致性能的可以再细致的缩小边界, 但是务必注意不要遗漏正确的答案区域导致出错;
解题代码
-
朴素二分法代码:
/** * 典型二分查找的题目 * 执行用时: 6 ms , 在所有 Java 提交中击败了 99.30% 的用户 * 内存消耗: 14.75 MB , 在所有 Java 提交中击败了 14.75% 的用户 */ public int minEatingSpeed(int[] piles, int h) { int left = 1, right = 0; for (int p : piles) { right = Math.max(p, right); } int ans = left; while (left <= right) { int mid = left + ((right - left) >> 1); if (eatAllWithinH(piles, h, mid)) { ans = mid; right = mid - 1; } else { left = mid + 1; } } return ans; } private boolean eatAllWithinH(int[] piles, int h, int speed) { int cnt = 0; for (int p : piles) { // 吃掉一堆的小时数是piles[i]/speed向上取整的值 cnt += (p - 1) / speed + 1; if (cnt > h) { return false; } } return true; }
-
更细致的二分范围代码:
/** * 对复杂度没影响, 但是能稍微优化些的二分范围值 * 执行用时: 1 ms , 在所有 Java 提交中击败了 99.69% 的用户 * 内存消耗: 43.99 MB , 在所有 Java 提交中击败了 64.63% 的用户 */ public int minEatingSpeed(int[] piles, int h) { long sum = 0; for (int p : piles) { sum += p; } int left = (int) ((sum - 1) / h) + 1; int right = (int) (sum / (h - piles.length + 1)) + 1; int ans = left; while (left <= right) { int mid = left + ((right - left) >> 1); if (eatAllWithinH(piles, h, mid)) { ans = mid; right = mid - 1; } else { left = mid + 1; } } return ans; }