1. 题目
袋子里最少数目的球
给你一个整数数组 nums ,其中 nums[i] 表示第 i 个袋子里球的数目。同时给你一个整数 maxOperations 。
你可以进行如下操作至多 maxOperations 次:
你的开销是单个袋子里球数目的 最大值 ,你想要 最小化 开销。
请你返回进行上述操作后的最小开销。
示例 1:
示例 2:
示例 3:
提示:
- 1 < = n u m s . l e n g t h < = 1 0 5 1 <= nums.length <= 10^5 1<=nums.length<=105
- 1 < = m a x O p e r a t i o n s , n u m s [ i ] < = 1 0 9 1 <= maxOperations, nums[i] <= 10^9 1<=maxOperations,nums[i]<=109
2. 解题思路
这题需要求解在操作maxOperations次内,求操作后数组内数的最大值的最小值。遇到求解最大值的最小值的问题或者最小值的最大值问题可以联想到利用二分查找。
但是二分查找需要的是有序数组,这题提供的nums是无序的,所以我们无法通过直接对nums进行查找。
我们转换一下求解问题的思路,题目要求是求解出最大值的最小值,我们不直接求解最小的值,我们从答案入手。
怎么从答案入手呢?试想一下,题目提供的 n u m s [ i ] nums[i] nums[i]是在区间 [ 1 , 1 0 9 ] [1,10^9] [1,109]内的,所以无论我们怎么把 n u m [ i ] num[i] num[i]拆分,我们最后拆分得到的数一定是在区间 [ 1 , 1 0 9 ] [1,10^9] [1,109]内的。所以,如果我们从1开始到 1 0 9 10^9 109逐一验证每一个数,看能不能在题目要求的maxOperations内得到,如果可以,那成立的最小的那个数自然就是答案了。
转换问题思路后不就能用二分查找了吗, [ 1 , 1 0 9 ] [1,10^9] [1,109]组成的数组不就是有序的吗?
但,我们每次都是从 [ 1 , 1 0 9 ] [1,10^9] [1,109]去二分吗?聪明的你肯定想到了可以从 [ 1 , m a x _ e l e m e n t ( n u m s ) ] [1,max\_element(nums)] [1,max_element(nums)]( m a x _ e l e m e n t max\_element max_element表示最大元素值)去二分查找,最终的答案肯定不会超过 n u m s nums nums中的最大值。
二分查找有套路,推荐一些学习资料,这里就直接用了,就不赘述啦~:
- 面试必考的「二分算法」系统梳理
- 蓝桥杯算法竞赛系列第四章——二分算法
本题解决了转换问题这一个关键步骤后,其次最为关键的就是判断某个答案是否能在maxOperations操作次数内得到,即check函数。先说下整体思路,然后再通过几个例子讲解下check函数。
check函数思路:
- 累加所有大于等于被check的数需要操作的次数
- 判断总的操作次数并返回True or False
如果不太明白,可以先看看代码,然后再看下例子
C++ check函数
//检查是否能在maxOperations内将数分成最大值为data
bool check(int data){
// nums_global 是升序排列后的nums
// first_mt 是第一个大于等于data数的下标
int first_mt = lower_bound(nums_global.begin(), nums_global.end(), data) - nums_global.begin();
int tmp_max_operation = 0;
int len = nums_global.size();
for (int i = first_mt; i < len; ++i){
// ceil 向上取整
tmp_max_operation += (ceil(nums_global[i]/1.0/data)-1);
}
return tmp_max_operation<=max_op;
}
Python check函数
# check函数
def check(data: int, global_nums : List[int], global_maxOperations: int) -> bool:
first_mt = Solution.lower_bound(global_nums, data)
nums_len = len(global_nums)
sum_operation = 0
for i in range(first_mt, nums_len):
sum_operation += (ceil(global_nums[i]*1.0/data) - 1)
return sum_operation <= global_maxOperations
如果看了代码还不是很明白,再来看看例子:
3. 代码
C++代码
class Solution {
public:
int max_op;
vector<int> nums_global;
int minimumSize(vector<int>& nums, int maxOperations) {
max_op = maxOperations;
sort(nums.begin(), nums.end());
nums_global = nums;
int max_value = *max_element(nums.begin(), nums.end());
// 二分查找
int left = 1;
int right = max_value;
int min_ans = 1e9+1;
while(left <= right){
int mid = left + ((right-left)>>1);
// >> 1, 表示右移1位,即除2,位运算速度快
// 如果check成立,则继续查找比他小的数
// 如果不成立则查找比他大的数,原因是结果可能是比mid大的,所以需要check比mid大的数
if (check(mid)){
min_ans = min(mid, min_ans);
right = mid-1;
}
else{
left = mid +1;
}
}
return min_ans;
}
//检查是否能在maxOperations内将数分成最大值为data
bool check(int data){
// nums_global 是升序排列后的nums
// first_mt 是第一个大于等于data数的下标
int first_mt = lower_bound(nums_global.begin(), nums_global.end(), data) - nums_global.begin();
int tmp_max_operation = 0;
int len = nums_global.size();
for (int i = first_mt; i < len; ++i){
// ceil 向上取整
tmp_max_operation += (ceil(nums_global[i]/1.0/data)-1);
}
return tmp_max_operation<=max_op;
}
};
Python代码
class Solution:
#返回nums中第一个>=target的值得位置,如果nums中都比target小,则返回len(nums)
def lower_bound(nums, target):
left, right = 0, len(nums)-1
pos = len(nums)
while left<right:
mid = left + ((right-left)>>1)
if nums[mid] < target:
left = mid+1
else:#>=
right = mid
if nums[left]>=target:
pos = left
return pos
# check函数
def check(data: int, global_nums : List[int], global_maxOperations: int) -> bool:
first_mt = Solution.lower_bound(global_nums, data)
nums_len = len(global_nums)
sum_operation = 0
for i in range(first_mt, nums_len):
sum_operation += (ceil(global_nums[i]*1.0/data) - 1)
return sum_operation <= global_maxOperations
def minimumSize(self, nums: List[int], maxOperations: int) -> int:
# sort nums
nums.sort()
max_value = nums[-1]
# 二分查找
left = 1
right = max_value
min_ans = 1e9+1
while(left <= right):
mid = left + ((right-left)>>1)
if (Solution.check(mid, nums, maxOperations)):
min_ans = min(mid, min_ans)
right = mid-1
else:
left = mid+1
return min_ans