题目描述
题目链接:[LeetCode 1760]袋子里最少数目的球
给你一个整数数组 nums ,其中 nums[i] 表示第 i 个袋子里球的数目。同时给你一个整数 maxOperations 。
你可以进行如下操作至多 maxOperations 次:
- 选择任意一个袋子,并将袋子里的球分到 2 个新的袋子中,每个袋子里都有 正整数 个球。
- 比方说,一个袋子里有 5 个球,你可以把它们分到两个新袋子里,分别有 1 个和 4 个球,或者分别有 2 个和 3 个球。
你的开销是单个袋子里球数目的 最大值 ,你想要 最小化 开销。
请你返回进行上述操作后的最小开销。
示例1
输入:nums = [9], maxOperations = 2
输出:3
解释:
- 将装有 9 个球的袋子分成装有 6 个和 3 个球的袋子。[9] -> [6,3] 。
- 将装有 6 个球的袋子分成装有 3 个和 3 个球的袋子。[6,3] -> [3,3,3] 。
装有最多球的袋子里装有 3 个球,所以开销为 3 并返回 3 。
示例2
输入:nums = [2,4,8,2], maxOperations = 4
输出:2
解释:
- 将装有 8 个球的袋子分成装有 4 个和 4 个球的袋子。[2,4,8,2] -> [2,4,4,4,2] 。
- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,4,4,4,2] -> [2,2,2,4,4,2] 。
- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,2,2,4,4,2] -> [2,2,2,2,2,4,2] 。
- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,2,2,2,2,4,2] -> [2,2,2,2,2,2,2,2] 。
装有最多球的袋子里装有 2 个球,所以开销为 2 并返回 2 。
示例3
输入:nums = [7,17], maxOperations = 2
输出:7
提示
1 <= nums.length <=
1
0
5
10^5
105
1 <= maxOperations, nums[i] <=
1
0
9
10^9
109
思路分析
数据范围是 1 0 5 10^5 105,因此控制算法的时间复杂度在O(nlogn)这个级别以下,第一时间想到的是贪心+优先队列,就每次处理最大值,发现这条路走不通,最后通过二分解决
要理解二分的核心思想:
1.具有某种性质
2.边界点
具体到这一道题,我们可以定义:
在边界点右边的所有点,它的操作次数都要小于或等于maxOperations,因为当每个袋子的开销变大,它的分割次数会变小
例如:如果开销是5,某个袋子是25,那么需要划分4次,才能让每个袋子都是5
如果开销增大,变成10,那么只需要划分2次,即[10, 10, 5]或者[8, 8, 9]
好,性质找出来了,然后第二步就是找边界点:
情况1:如果mid在区间内,如图所示,下一步,R自然要移动到R’的位置,即R = mid
情况2:如果mid不在区间内,如图所示,下一步,L移动到L’的位置,即L’=mid + 1
至于为什么L’不是变成mid,因为情况1中,边界点也是满足cnt <= maxOperations条件的,如果mid刚好等于边界点,那么R = mid - 1就会找不到边界点
而情况2中,mid不满足条件,显然它不可能是边界点,从而L’设为mid + 1
代码
class Solution {
public:
int minimumSize(vector<int>& nums, int maxOperations) {
int n = nums.size(), m = -1;
//m寻找装有最大数量的球的袋子,作为右边界
for(int i = 0; i < n; i++) m = max(m, nums[i]);
int l = 1, r = m;
while(l < r) {
//cnt记录代价为mid时,需要使用的步数
int mid = l + r >> 1, cnt = 0;
for(int i = 0; i < n; i++)
//步数为nums[i] / mid上取整 - 1
//例如袋子里有9个,代价为3, 只需要分9 / 3 - 1 = 2次就行
//袋子里有9个,代价为4, 也需要(9 / 4)上取整 - 1 = 2次
cnt += (nums[i] + mid - 1) / mid - 1;
//看之前的分析,就很清楚了
if(cnt <= maxOperations) r = mid;
else l = mid + 1;
}
return l;
}
};