这题体现了二分的一种典型应用: “猜答案”
如果没做到类似的题,很难第一时间想到用二分去处理,针对这类问题只有多总结,找到不同题目间共性,把本质提取出来,才能在第一时间条件反射般看出它是二分。
拿这道题来说,抓准题目中的关键字:【最大值】,【最小】
即使得,满足要求的答案的最大值最小。首先直观感受是,这个最大值太模糊了,根本不知道具体怎么去取这个最大值,怎么定义什么样的值才算最大值,怎么样才能找到那个尽可能大的值,而且保证最大值之间取最小。
如果看完题目有上面的困惑时,这个时候再回看题目的限制条件,这个限制条件就是我们去找那个所谓的 “最大值” 的唯一途径。
题目的限制条件是:将其中一个袋子(数组中的每个元素表示一个袋子)里的球,分到 2 个袋子中去,并且还加了一个非常重要的限制:操作次数,即如果操作次数大于1,我们再分多次
以样例为例:起初只有一个袋子,袋子里有9个球,分的操作次数为2,假设我们随便先分一次:
第一次:9:4 5, 即从一号袋子中拿出5个放入另外一个袋子,此时我们消耗了 1 个操作次数
第二次:这时我们选择 二号袋子(随意模拟的,只为解读题意),从二号袋子中拿3个球到 3 号袋子中,
这时三个袋子中的球分别为:4 2 3
我们要做的就是在 “随意” 模拟的过程中,把满足题目要求的值 “猜” 出来。
如何猜?
猜,也是有策略性的猜,因为数据范围比较大,为了加速猜取,我们这里就自然想到了用二分,每次排除一半不可能的值。
处理过程中的细节点,看代码注释
int[] nums;
int n;
public int minimumSize(int[] _nums, int k) {
nums = _nums;
n = nums.length;
int l = 1, r = (int)1e9;
while(l < r) {
int mid = l + r >> 1;
/**
这里是个重点,体现了 “猜” 字,即我们【假设】当前的 mid 就是最终我们要求的那个值,
但这个值具体是不是【最合适】的那个答案,还不一定,要做进一步验证
**/
if(check(mid, k)) r = mid;
else l = mid + 1;
}
return l;
}
boolean check(int mid ,int k) {
int cnt = 0;
for(int x : nums) {
/**
假设我们最终要将每个袋子中放mid个求,那么要操作几次
比如 x = 9, mid = 3. 即9个球,最终要保证每个袋子里【至少】有3个求,要操作几次?
9:3 6 => 6: 3 3 可以看到一共需要两次操作,因为 9 % 3 == 0,因此需要 (9 / 3 - 1)次
如果不能整除,比如 x = 10, mid = 3
10: 3 7 => 7: 3 4 => 4: 3 1 从模拟过程可以看到至少需要3次,才能尽最大可能保证每个袋子有3个球,即 10/3 = 3
**/
cnt += x / mid;
if(x % mid == 0) cnt--;
//如果cnt 太大,说明操作次数过多,操作次数过多说明 mid 太小,要适当加大 mid
if(cnt > k) return false;
}
return true;
}
}