LeetCode 1760 - 袋子里最少的球
题目
给你一个整数数组 nums,其中 nums[i] 表示第 i 个袋子里球的数目。同时给你一个整数 maxOperations。
你可以进行如下操作至多 maxOperations 次:
选择任意一个袋子,并将袋子里的球分到 2 个新的袋子中,每个袋子里都有正整数个球。
比如一个袋子里有 5 个球,可以分成 1 和 4,或者 2 和 3。
你的开销是单个袋子里球数目的最大值,你想要最小化开销。请返回最小开销。
示例 1
输入:nums = [9], maxOperations = 2
输出:3
解释:[9] -> [6,3] -> [3,3,3],最大值为 3。
示例 2
输入:nums = [2,4,8,2], maxOperations = 4
输出:2
解释:最终可分为 [2,2,2,2,2,2,2,2],最大值为 2。
示例 3
输入:nums = [7,17], maxOperations = 2
输出:7
解题思路
这道题可以使用二分查找来解决:
题目分析:
- 我们需要找到一个最小值 x,使得所有袋子里的球数都不超过 x
- 对于每个袋子,如果球数超过 x,需要进行分割操作
- 分割操作次数不能超过 maxOperations
二分查找思路:
- 对于给定的目标值 x,可以计算需要多少次操作才能使所有袋子的球数 ≤ x
- 如果操作次数 ≤ maxOperations,说明 x 可能还可以更小
- 如果操作次数 > maxOperations,说明 x 必须更大
关键点:
- 二分查找的左边界是 1
- 右边界是数组中的最大值
- 对于每个 x,计算需要的操作次数
代码实现
1 | class Solution: |
代码解释
check(mid)函数:- 计算将所有球数控制在mid以下需要的操作次数
- 对于每个袋子,需要的操作次数是
(num-1) // mid - 如果总操作次数 ≤ maxOperations,返回True
二分查找过程:
- 初始范围:
[1, max(nums)] - 每次取中间值mid
- 根据check(mid)的结果缩小范围
- 最终找到满足条件的最小值
- 初始范围:
复杂度分析
时间复杂度:O(n * log M)
- n 是数组长度
- M 是数组中的最大值
- 二分查找需要 log M 次迭代
- 每次迭代需要 O(n) 时间计算操作次数
空间复杂度:O(1)
- 只使用了常数额外空间
总结
这是一道典型的二分查找应用题。关键在于:
- 将问题转化为判定性问题
- 设计合适的判定函数
- 正确处理二分查找的边界条件
关键代码详解
1 | operations = sum((num - 1) // mid for num in nums) |
这行代码计算需要多少次操作才能使所有袋子的球数不超过mid。让我们逐部分分析:
(num - 1) // mid的含义:- 假设一个袋子有 num 个球,目标是将其分成若干个不超过 mid 的小袋
- 例如:num = 9, mid = 3
- (9-1) // 3 = 8 // 3 = 2,表示需要2次操作
- 第一次:9 -> [3,6]
- 第二次:6 -> [3,3]
- 最终:[3,3,3]
为什么是
(num - 1) // mid:- 如果 num = 7, mid = 3
- (7-1) // 3 = 6 // 3 = 2 次操作
- 第一次:7 -> [3,4]
- 第二次:4 -> [3,1]
- 最终:[3,3,1]
整数除法
//的作用:- 向下取整,确保得到最少需要的操作次数
- 例如:5/2 = 2.5,而 5//2 = 2
列表推导式
for num in nums:- 对数组中的每个数字都进行上述计算
sum()函数:- 将所有需要的操作次数加总
- 得到处理整个数组需要的总操作次数
示例:
1 | nums = [9, 5] |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Never Settle!
评论