1760. 袋子里最少数目的球 Medium
给你一个整数数组 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
解题思路
输入: 一个整数数组 nums
和一个操作数 maxOperations
,每次操作可以将一个袋子的球分到两个袋子去
输出: 在限定操作数内,所有袋子中的最大值就是开销,返回最小开销
本题属于二分答案类问题。
这道题和 875 题类似,都是找到一个 最大/最小 值满足 小于等于某个值 。
对于一个袋子里有 a
个球,如果我们想让这个袋子中所有子袋都 ≤ x
:
- 需要的操作数是:
(a - 1) // x
- 把它记下来,累加所有袋子的操作次数。
- 如果
总操作次数 ≤ maxOperations
,说明这个x
是可行的。
利用二分法我们可以从 [1, max(nums)]
中寻找答案
当 mid
满足条件将答案记录下来,然后移动右指针将范围缩至 [left, mid - 1]
再次寻找更小的答案
反之则移动左指针到 [mid + 1, right]
寻找答案
最后当 left > right
时候说明已经全部检查完毕,返回最后一次记录的答案
代码实现
from typing import List
class Solution:
def minimumSize(self, nums: List[int], maxOperations: int) -> int:
# 辅助函数:判断当最大球数限制为 x 时,是否可以在 maxOperations 次操作内完成拆分
def check(x):
operations = 0 # 记录总操作次数
for num in nums:
# 如果一个袋子里有 num 个球,我们要把它拆成多个小袋,
# 每个小袋最多 x 个球,拆成的袋子数为 ceil(num / x)
# 所需的操作数 = 拆成几份 - 1 = ceil(num / x) - 1
# 这个公式可以用 (num - 1) // x 计算,避免浮点数
operations += (num - 1) // x
# 如果总操作次数不超过限制,说明这个 x 是可行的
return operations <= maxOperations
# 二分查找的左右边界,最小可能值是 1,最大值是原始袋子中球数最多的那个
left, right = 1, max(nums)
ans = right # 初始化答案为最大值,逐步缩小
while left <= right:
mid = (left + right) // 2 # 取中间值,作为当前尝试的最大球数上限
if check(mid):
# 如果当前最大球数限制 mid 是可行的,尝试更小的(优化答案)
ans = mid
right = mid - 1
else:
# 如果不行,说明限制太严格了,需要放宽限制
left = mid + 1
return ans # 返回最小可行的最大球数
/**
* @param {number[]} nums
* @param {number} maxOperations
* @return {number}
*/
var minimumSize = function(nums, maxOperations) {
function check(x) {
let operations = 0;
for (let num of nums) {
operations += Math.floor((num - 1) / x);
}
return operations <= maxOperations;
}
let left = 1;
let right = Math.max(...nums);
let ans = 0;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (check(mid)) {
ans = mid;
right = mid - 1
} else {
left = mid + 1;
}
}
return ans;
};
复杂度分析
时间复杂度:O(nlog m)
空间复杂度:O(1)