二分查找
直观思维是对每个袋子进行分球操作,这样枚举似乎无从下手。逆向思维,从 1 1 1 开始,枚举袋子里球的最大数量,可以计算划分每个袋子需要的最小次数,通过最小次数之和,判断方案是否可行。由于从小到大枚举,第一个可行的方案就是所求。枚举时间复杂度 O ( m ) O(m) O(m) , m m m 是初始状态所有袋子里的最大球数,最多枚举到 m m m ,再往上枚举就没有意义了。二分查找可以优化枚举,时间复杂度 O ( l o g m ) O(logm) O(logm) 。
对于每个枚举(查找),需要计算划分次数之和,计算每个袋子的划分次数,需要遍历整个数组,时间复杂度 O ( n ) O(n) O(n) , n n n 是数组大小。枚举(查找)和遍历数组是乘积关系,总时间复杂度 O ( n l o g m ) O(nlogm) O(nlogm) 。
提示 : 利用公式(上取整) 计算每个袋子的划分次数。 上取整公式
=
⌈
x
m
i
d
−
1
⌉
=
⌊
x
+
m
i
d
−
1
m
i
d
−
1
⌋
=
⌊
x
−
1
m
i
d
⌋
=\lceil\frac{x}{mid}-1\rceil=\lfloor\frac{x+mid-1}{mid}-1\rfloor=\lfloor\frac{x-1}{mid}\rfloor
=⌈midx−1⌉=⌊midx+mid−1−1⌋=⌊midx−1⌋
上取整的含义 : 小球总数为
x
x
x 的袋子,划分成若干 小球总数为
m
i
d
mid
mid 的袋子,需要的最小次数。
上取整转化下取整公式有严格证明,建议读者背住。
CPP
class Solution {
public:
int minimumSize(vector<int>& nums, int maxOperations) {
int l = 1, r= *max_element(nums.begin(),nums.end());
while(l<=r){
int mid = l + (r-l>>1);
int sum = 0;
for(auto &x:nums) sum += (x-1)/mid;
if(sum>maxOperations) l = mid + 1;
else r = mid - 1;
}return l;
}
};
C
int minimumSize(int* nums, int numsSize, int maxOperations){
int left = 1,right = 0,mid = 0;
for(int i = 0;i<numsSize;i++){
if(right<nums[i]){//找到所有桶中最大值
right = nums[i];//最大值作为右边界
}
}
while(left<=right){
mid = left + ((right - left)>>1);//以mid为桶的最大值划分
long long opt = 0;//需要的拆分次数
for(int i = 0;i<numsSize;i++){
opt += (nums[i] - 1)/mid;//等于下面三行
//opt+=nums[i]/mid;//不能整除时,需要的拆分次数
//if(0==nums[i]%mid)//能整除时,少拆分一次。
// opt--;
}
if(opt > maxOperations){//操作次数太多了,增加每个桶的最大值,以减少操作次数
left = mid + 1;//左边界右移
}else{
right = mid -1;//右边界左移
}
}
return left;//返回划分后,桶中最大值
}
- 时间复杂度 : O ( n l o g m ) O(nlogm) O(nlogm) , n n n 是数组长度, m m m 是数组中的最大值,二分查找,对每个查找遍历数组的时间复杂度 O ( n l o g m ) O(nlogm) O(nlogm) 。
- 空间复杂度 : O ( 1 ) O(1) O(1) , 只使用常量级空间 。
AC
致语
- 二分查找是简单算法,但是要手熟。
- 理解思路很重要!
- 欢迎读者在评论区留言,墨染看到就会回复的。