Skip to content

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 时候说明已经全部检查完毕,返回最后一次记录的答案

代码实现

python
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  # 返回最小可行的最大球数
javascript
/**
 * @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)

链接

1760 国际版

1760 中文版