[LeetCode 1760]袋子里最少数目的球

该博客介绍了如何解决LeetCode上的一个算法问题,即在限定操作次数内,如何通过分割球使得每个袋子的球数不超过特定开销,以达到最小开销。作者分析了问题的性质,提出了采用二分查找的解决方案。通过不断调整开销的边界,确定在不超过最大操作次数的情况下,能够达到的最小开销。博客内容包括思路解析、边界条件分析以及代码实现,对于理解和解决此类问题具有指导意义。
摘要由CSDN通过智能技术生成

题目描述

题目链接:[LeetCode 1760]袋子里最少数目的球

给你一个整数数组 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

提示

1 <= nums.length <= 1 0 5 10^5 105
1 <= maxOperations, nums[i] <= 1 0 9 10^9 109

思路分析

数据范围是 1 0 5 10^5 105,因此控制算法的时间复杂度在O(nlogn)这个级别以下,第一时间想到的是贪心+优先队列,就每次处理最大值,发现这条路走不通,最后通过二分解决

要理解二分的核心思想:
1.具有某种性质
2.边界点

具体到这一道题,我们可以定义:
在边界点右边的所有点,它的操作次数都要小于或等于maxOperations,因为当每个袋子的开销变大,它的分割次数会变小
例如:如果开销是5,某个袋子是25,那么需要划分4次,才能让每个袋子都是5
如果开销增大,变成10,那么只需要划分2次,即[10, 10, 5]或者[8, 8, 9]
在这里插入图片描述

好,性质找出来了,然后第二步就是找边界点:
情况1:如果mid在区间内,如图所示,下一步,R自然要移动到R’的位置,即R = mid
在这里插入图片描述

情况2:如果mid不在区间内,如图所示,下一步,L移动到L’的位置,即L’=mid + 1
至于为什么L’不是变成mid,因为情况1中,边界点也是满足cnt <= maxOperations条件的,如果mid刚好等于边界点,那么R = mid - 1就会找不到边界点

而情况2中,mid不满足条件,显然它不可能是边界点,从而L’设为mid + 1
在这里插入图片描述

代码

class Solution {
public:
    int minimumSize(vector<int>& nums, int maxOperations) {
        int n = nums.size(), m = -1;
        //m寻找装有最大数量的球的袋子,作为右边界
        for(int i = 0; i < n; i++) m = max(m, nums[i]);
        int l = 1, r = m;
        while(l < r) {
        	//cnt记录代价为mid时,需要使用的步数
            int mid = l + r >> 1, cnt = 0;
            for(int i = 0; i < n; i++)
            	//步数为nums[i] / mid上取整 - 1
            	//例如袋子里有9个,代价为3, 只需要分9 / 3 - 1 = 2次就行
            	//袋子里有9个,代价为4, 也需要(9 / 4)上取整 - 1 = 2次
                cnt += (nums[i] + mid - 1) / mid - 1;
            //看之前的分析,就很清楚了
            if(cnt <= maxOperations) r = mid;
            else l = mid + 1; 
        }

        return l;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值