|
| 1 | +### 题目描述 |
| 2 | + |
| 3 | +这是 LeetCode 上的 **[1760. 袋子里最少数目的球](https://acoier.com/2022/12/23/1760.%20%E8%A2%8B%E5%AD%90%E9%87%8C%E6%9C%80%E5%B0%91%E6%95%B0%E7%9B%AE%E7%9A%84%E7%90%83%EF%BC%88%E4%B8%AD%E7%AD%89%EF%BC%89/)** ,难度为 **中等**。 |
| 4 | + |
| 5 | +Tag : 「二分」 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +给你一个整数数组 `nums`,其中 `nums[i]` 表示第 `i` 个袋子里球的数目。同时给你一个整数 `maxOperations`。 |
| 10 | + |
| 11 | +你可以进行如下操作至多 `maxOperations` 次: |
| 12 | + |
| 13 | +* 选择任意一个袋子,并将袋子里的球分到 `2` 个新的袋子中,每个袋子里都有 正整数 个球。 |
| 14 | + * 比方说,一个袋子里有 `5` 个球,你可以把它们分到两个新袋子里,分别有 `1` 个和 `4` 个球,或者分别有 `2` 个和 `3` 个球。 |
| 15 | + 你的开销是单个袋子里球数目的 最大值 ,你想要 最小化 开销。 |
| 16 | + |
| 17 | +请你返回进行上述操作后的最小开销。 |
| 18 | + |
| 19 | +示例 1: |
| 20 | +``` |
| 21 | +输入:nums = [9], maxOperations = 2 |
| 22 | +
|
| 23 | +输出:3 |
| 24 | +
|
| 25 | +解释: |
| 26 | +- 将装有 9 个球的袋子分成装有 6 个和 3 个球的袋子。[9] -> [6,3] 。 |
| 27 | +- 将装有 6 个球的袋子分成装有 3 个和 3 个球的袋子。[6,3] -> [3,3,3] 。 |
| 28 | +装有最多球的袋子里装有 3 个球,所以开销为 3 并返回 3 。 |
| 29 | +``` |
| 30 | +示例 2: |
| 31 | +``` |
| 32 | +输入:nums = [2,4,8,2], maxOperations = 4 |
| 33 | +
|
| 34 | +输出:2 |
| 35 | +
|
| 36 | +解释: |
| 37 | +- 将装有 8 个球的袋子分成装有 4 个和 4 个球的袋子。[2,4,8,2] -> [2,4,4,4,2] 。 |
| 38 | +- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,4,4,4,2] -> [2,2,2,4,4,2] 。 |
| 39 | +- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,2,2,4,4,2] -> [2,2,2,2,2,4,2] 。 |
| 40 | +- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,2,2,2,2,4,2] -> [2,2,2,2,2,2,2,2] 。 |
| 41 | +装有最多球的袋子里装有 2 个球,所以开销为 2 并返回 2 。 |
| 42 | +``` |
| 43 | +示例 3: |
| 44 | +``` |
| 45 | +输入:nums = [7,17], maxOperations = 2 |
| 46 | +
|
| 47 | +输出:7 |
| 48 | +``` |
| 49 | + |
| 50 | +提示: |
| 51 | +* $1 <= nums.length <= 10^5$ |
| 52 | +* $1 <= maxOperations, nums[i] <= 10^9$ |
| 53 | + |
| 54 | +--- |
| 55 | + |
| 56 | +### 二分 |
| 57 | + |
| 58 | +最小化不超过 `max` 次划分操作后的单个袋子最大值,我们将其称为「划分值」。 |
| 59 | + |
| 60 | +**答案具有二段性:若使用 $k \leq \max$ 次划分操作后可达到最小划分值,此时减少划分操作次数,会使得划分值非单调上升。** |
| 61 | + |
| 62 | +因此我们可以二分答案,从而将问题进行等价转换: |
| 63 | + |
| 64 | +**假设当前二分到的值为 $limit$,我们需要实现一个线性复杂度为 `check` 函数,判断能否使用不超过 $\max$ 次划分次数,来使得划分值不超过 $limit$**: |
| 65 | + |
| 66 | +* 若能满足,说明 $[limit, +\infty]$ 范围的划分值,均能使用不超过 $\max$ 次的实现,此时让 $r = limit$ |
| 67 | +* 若不能满足,比 $limit$ 更小的划分值,则更无法在 $\max$ 次操作中满足,说明 $[1, limit]$ 范围划分值均不是答案,此时让 $l = limit + 1$ |
| 68 | + |
| 69 | +考虑如何实现 `check` 函数,从前往后处理每个 $nums[i]$,根据 $nums[i]$ 与当前限制 $limit$ 的大小关系进行分情况讨论: |
| 70 | + |
| 71 | +* 若 $nums[i] \leq limit$:说明当前袋子不会成为瓶颈,无须消耗划分次数 |
| 72 | +* 若 $nums[i] > limit$:此时需要对当前袋子进行划分,直到满足单个袋子球的数量不超过 $limit$ 为止,由于每次划分相当于增加一个袋子,而将 $nums[i]$ 划分成若干个不超过 $limit$ 个球的袋子,需要 $\left \lceil \frac{nums[i]}{limit} \right \rceil$ 个袋子,减去原本的一个,共需要增加 $\left \lceil \frac{nums[i]}{limit} \right \rceil$ 个新袋子,即划分 $\left \lceil \frac{nums[i]}{limit} \right \rceil$ 次 |
| 73 | + |
| 74 | + |
| 75 | +Java 代码: |
| 76 | +```Java |
| 77 | +class Solution { |
| 78 | + public int minimumSize(int[] nums, int max) { |
| 79 | + int l = 1, r = 0x3f3f3f3f; |
| 80 | + while (l < r) { |
| 81 | + int mid = l + r >> 1; |
| 82 | + if (check(nums, mid, max)) r = mid; |
| 83 | + else l = mid + 1; |
| 84 | + } |
| 85 | + return r; |
| 86 | + } |
| 87 | + boolean check(int[] nums, int limit, int max) { |
| 88 | + int cnt = 0; |
| 89 | + for (int x : nums) cnt += Math.ceil(x * 1.0 / limit) - 1; |
| 90 | + return cnt <= max; |
| 91 | + } |
| 92 | +} |
| 93 | +``` |
| 94 | +TypeScript 代码: |
| 95 | +```TypeScript |
| 96 | +function minimumSize(nums: number[], max: number): number { |
| 97 | + function check(nums: number[], limit: number, max: number): boolean { |
| 98 | + let cnt = 0 |
| 99 | + for (const x of nums) cnt += Math.ceil(x / limit) - 1 |
| 100 | + return cnt <= max |
| 101 | + } |
| 102 | + let l = 1, r = 0x3f3f3f3f |
| 103 | + while (l < r) { |
| 104 | + const mid = l + r >> 1 |
| 105 | + if (check(nums, mid, max)) r = mid |
| 106 | + else l = mid + 1 |
| 107 | + } |
| 108 | + return r |
| 109 | +} |
| 110 | +``` |
| 111 | +Python 代码: |
| 112 | +```Python |
| 113 | +class Solution: |
| 114 | + def minimumSize(self, nums: List[int], maxv: int) -> int: |
| 115 | + def check(nums, limit, maxv): |
| 116 | + return sum([(x + limit - 1) // limit - 1 for x in nums]) <= maxv |
| 117 | + l, r = 1, 0x3f3f3f3f |
| 118 | + while l < r: |
| 119 | + mid = l + r >> 1 |
| 120 | + if check(nums, mid, maxv): |
| 121 | + r = mid |
| 122 | + else: |
| 123 | + l = mid + 1 |
| 124 | + return r |
| 125 | +``` |
| 126 | +* 时间复杂度:$O(n \log{M})$,其中 $M = 1e9$ 为值域大小 |
| 127 | +* 空间复杂度:$O(1)$ |
| 128 | + |
| 129 | +--- |
| 130 | + |
| 131 | +### 最后 |
| 132 | + |
| 133 | +这是我们「刷穿 LeetCode」系列文章的第 `No.1758` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 |
| 134 | + |
| 135 | +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 |
| 136 | + |
| 137 | +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 |
| 138 | + |
| 139 | +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 |
| 140 | + |
0 commit comments