注意:
1 <= nums.length <= 105
1 <= maxOperations, nums[i] <= 109
本题要求求解最小化开销,很容易被误导为使用贪心法解题
贪心方法好用且易理解,但贪心法需要有足够的把握才可以使用
笔者首先使用了贪心的办法发现无论如何都没法通过测试,每次都会有几个特殊的例子来卡住贪心法
本题难在对二分查找方法的高度抽象
与此同时我们知道了每个袋子内球的个数:
设最终的最小化开销为 y e n d y_{end} yend
所有袋子中的球通过maxOperations次操作得到的 y i y_i yi都小于 y e n d y_{end} yend
### 问题转化:
❗我们可以求出 y e n d y_{end} yend的一个范围,在 y e n d y_{end} yend的范围内进行枚举( y i y_i yi),计算出所有球的最小化开销全部转化为 ≤ y i \leq y_i ≤yi(全部都小于等于yi)的次数,求出最接近maxOperation的次数
y e n d y_{end} yend的边界计算:带入极端情况
- 上界:maxOperations一次都不进行操作, y e n d = n u m s m a x y_{end} = nums_{max} yend=numsmax
- 下界:maxoperations操作次数拉满,假设操作 + ∞ +\infty +∞次,则 y e n d = 1 y_{end} = 1 yend=1
上下界确定完成!
每个求转化为枚举最小开销的次数由下述代码计算:
for .......
pos += (nums[i] - 1) / yi
// ‘-1’ 的目的是边界数据处理
枚举?还有没有更快的办法?
我们可以将数组中的球数目进行排序,然后进行二分查找符合条件的值
设模拟的操作次数为pos
符合条件即为: p o s ≤ m a x O p e r a t i o n s pos\leq maxOperations pos≤maxOperations
我们查找起点为:left = 1 -----> y e n d y_{end} yend下界
查找终点:right = n u m s m a x nums_{max} numsmax —> y e n d y_{end} yend上界
如此一来我们便将这个题转化成一个二分查找题目:
代码如下:
class Solution {
public:
int minimumSize(vector<int>& nums, int maxOperations) {
sort(nums.begin(), nums.end());
int left = 1, right = nums[nums.size()-1]; // 取上下界
int ans = 0;
while (left <= right) {
int y = (left + right) / 2;
long long ops = 0; // 数据大,小心溢出
for (int x: nums) {
ops += (x - 1) / y; // 每次都计算次数
}
if (ops <= maxOperations) { // 如果合适则将值保存起来
ans = y;
right = y - 1; // 范围缩小
}
else {
left = y + 1; // 范围缩小
}
}
return ans;
}
};
来自:leetcode
部分图片来自leetcode solution