
這題體現(xiàn)了二分的一種典型應(yīng)用: “猜答案”
如果沒做到類似的題,很難第一時(shí)間想到用二分去處理,針對這類問題只有多總結(jié),找到不同題目間共性,把本質(zhì)提取出來,才能在第一時(shí)間條件反射般看出它是二分。
拿這道題來說,抓準(zhǔn)題目中的關(guān)鍵字:【最大值】,【最小】
即使得,滿足要求的答案的最大值最小。首先直觀感受是,這個(gè)最大值太模糊了,根本不知道具體怎么去取這個(gè)最大值,怎么定義什么樣的值才算最大值,怎么樣才能找到那個(gè)盡可能大的值,而且保證最大值之間取最小。
如果看完題目有上面的困惑時(shí),這個(gè)時(shí)候再回看題目的限制條件,這個(gè)限制條件就是我們?nèi)フ夷莻€(gè)所謂的 “最大值” 的唯一途徑。
題目的限制條件是:將其中一個(gè)袋子(數(shù)組中的每個(gè)元素表示一個(gè)袋子)里的球,分到 2 個(gè)袋子中去,并且還加了一個(gè)非常重要的限制:操作次數(shù),即如果操作次數(shù)大于1,我們再分多次
以樣例為例:起初只有一個(gè)袋子,袋子里有9個(gè)球,分的操作次數(shù)為2,假設(shè)我們隨便先分一次:
第一次:9:4 5, 即從一號(hào)袋子中拿出5個(gè)放入另外一個(gè)袋子,此時(shí)我們消耗了 1 個(gè)操作次數(shù)
第二次:這時(shí)我們選擇 二號(hào)袋子(隨意模擬的,只為解讀題意),從二號(hào)袋子中拿3個(gè)球到 3 號(hào)袋子中,
這時(shí)三個(gè)袋子中的球分別為:4 2 3
我們要做的就是在 “隨意” 模擬的過程中,把滿足題目要求的值 “猜” 出來。
如何猜?
猜,也是有策略性的猜,因?yàn)閿?shù)據(jù)范圍比較大,為了加速猜取,我們這里就自然想到了用二分,每次排除一半不可能的值。
處理過程中的細(xì)節(jié)點(diǎn),看代碼注釋
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;
/**
這里是個(gè)重點(diǎn),體現(xiàn)了 “猜” 字,即我們【假設(shè)】當(dāng)前的 mid 就是最終我們要求的那個(gè)值,
但這個(gè)值具體是不是【最合適】的那個(gè)答案,還不一定,要做進(jìn)一步驗(yàn)證
**/
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) {
/**
假設(shè)我們最終要將每個(gè)袋子中放mid個(gè)求,那么要操作幾次
比如 x = 9, mid = 3. 即9個(gè)球,最終要保證每個(gè)袋子里【至少】有3個(gè)求,要操作幾次?
9:3 6 => 6: 3 3 可以看到一共需要兩次操作,因?yàn)?9 % 3 == 0,因此需要 (9 / 3 - 1)次
如果不能整除,比如 x = 10, mid = 3
10: 3 7 => 7: 3 4 => 4: 3 1 從模擬過程可以看到至少需要3次,才能盡最大可能保證每個(gè)袋子有3個(gè)球,即 10/3 = 3
**/
cnt += x / mid;
if(x % mid == 0) cnt--;
//如果cnt 太大,說明操作次數(shù)過多,操作次數(shù)過多說明 mid 太小,要適當(dāng)加大 mid
if(cnt > k) return false;
}
return true;
}
}