二分搜索怎么用?我又总结了套路 :: labuladong的算法小抄 #1015
Replies: 43 comments 12 replies
-
东哥,int right = ... + 1;这个如果我不加一使用闭区间的时候为什么不正确呢 |
Beta Was this translation helpful? Give feedback.
-
Python Banana def minEatingSpeed(self, piles: List[int], h: int) -> int:
def f(piles, x):
hours = 0
for i in range(len(piles)):
hours += piles[i] // x
if piles[i]%x>0:
hours += 1
return hours
left = 1
right = max(piles)
while left < right:
mid = (right+left) // 2
if f(piles, mid) <= h:
right = mid
else:
left = mid + 1
return right |
Beta Was this translation helpful? Give feedback.
-
为啥js代码这段没过提交,我寻思着和那段java代码不是一样的吗, 珂珂吃香蕉那个题目 var minEatingSpeed = function(piles, h) {
let left = 1
let right = 1000000000 + 1
while(left < right) {
let mid = Math.floor(left + (right - left) / 2)
console.log(left, right, f(piles, mid))
if(f(piles, mid) <= h) {
right = mid
} else {
left = mid + 1
}
}
return left
// 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉
// f(x) 随着 x 的增加单调递减
function f(piles, x) {
let hours = 0
for(let i = 0;i < piles.length;i++) {
hours += piles[i] / x
if(piles[i] % x > 0) {
hours++
}
}
return hours
}
}; |
Beta Was this translation helpful? Give feedback.
-
/**
* @param {number[]} piles
* @param {number} h
* @return {number}
*/
var minEatingSpeed = function(piles, h) {
let left = 1;
let right = 1000000000;
while (left <= right) {
let mid = Math.floor(left + (right - left) / 2);
if (f(piles, mid) === h) {
// 搜索左侧边界,则需要收缩右侧边界
right = mid - 1;
} else if (f(piles, mid) < h) {
// 需要让 f(x) 的返回值大一些
right = mid - 1;
} else if (f(piles, mid) > h) {
// 需要让 f(x) 的返回值小一些
left = mid + 1;
}
}
return left;
}
// 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉
// f(x) 随着 x 的增加单调递减
function f(piles, x) {
let hours = 0;
for (let i = 0; i < piles.length; i++) {
hours += Math.floor(piles[i] / x);
if (piles[i] % x > 0) {
hours++;
}
}
return hours;
} |
Beta Was this translation helpful? Give feedback.
-
@lovelyJason f 函数你也要加上 Math.floor |
Beta Was this translation helpful? Give feedback.
-
爱吃香蕉的珂珂 python version import math
class Solution:
def minEatingSpeed(self, piles: List[int], h: int) -> int:
# 问题转化,搜索区间是[1,max_piles]
# nums[mid]用于计算以当前的速度吃完香蕉的时间是多少小时
# 需要求满足条件的最小speed,实际上就是左边界问题,因为不同的速度消耗时间是相同的
def f(piles, speed):
hours = 0
for pile in piles:
hours += math.ceil(pile / speed)
return hours
left, right = 1, max(piles) + 1
while left < right:
speed = left + (right - left) // 2
hours = f(piles, speed)
if hours > h: # 要加快速度
left = speed + 1
elif hours < h:
right = speed
elif hours == h:
right = speed
return left |
Beta Was this translation helpful? Give feedback.
-
# @lc code=start
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
def f(weights, capacity):
cur = 0
days = 0
for i, weight in enumerate(weights):
if cur + weight <= capacity:
cur += weight
else:
cur = weight
days += 1
return days + 1
# 至少有一天承载了一天最重的包裹,所以最小值是max(weights)
left, right = max(weights), sum(weights) + 1
while left < right:
capacity = left + (right - left) // 2
need_days = f(weights, capacity)
if need_days > days: # 增大容量
left = capacity + 1
elif need_days < days:
right = capacity
elif need_days == days:
right = capacity
return left |
Beta Was this translation helpful? Give feedback.
-
//java1011 class Solution {
public int shipWithinDays(int[] weights, int days) {
int left = 0, right = 0;
for(int weight : weights){
//最大运力保证刚好一次性运完所有包裹
right += weight;
//包裹不能拆开运所以至少保证载重能承载任意一个包裹;
left = Math.max(left, weight);
}
while(left < right){
int mid = left + (right - left) / 2;
if(weightSum(weights, mid) > days){
left = mid + 1;
}else{
right = mid;
}
}
return left;
}
public int weightSum(int[] weights, int speed){
int sum = 0;
int res = 0;
for(int weight : weights){
sum += weight;
if(sum > speed){
res += 1;
sum = weight;
}
}
//加上最后一个不超载无法触发sum > speed 要补1,如果超载则最后一个要单独运输一次还要补1;
return res + 1;
}
} |
Beta Was this translation helpful? Give feedback.
-
第三题有一点点区别,那个单调函数用了贪心的思想。 单调函数的入参是maxSum,返回值表示最少分割次数。 对于一个给定的最大和 maxSum,最少能分割 minSplitTimes 次,那么 minSplitTimes ≤ m说 明当前的maxSum满足要求,那么继续缩小 maxSum ,看看有没最佳(小)的值。 |
Beta Was this translation helpful? Give feedback.
-
这叫单调不增和单调不减,谢谢 |
Beta Was this translation helpful? Give feedback.
-
感谢,模板真好用 |
Beta Was this translation helpful? Give feedback.
-
class Solution {
public int splitArray(int[] nums, int m) {
//分析:求的是分组的数,和如何分组才能让某一组的组内元素之和是固定分组情况下所有情况的sum=min的min是多少
//1.m分的越多:最大:每个元素为一组,那此时的maxSum就是nums中的最大元素的值
//2.m分得越少:最小:整个nums为一组,此时的maxSum就是nums中所有元素之和
//所以我们在研究分组的数量和最终的maxSum的关系
if(nums==null||nums.length==0) return 0;
int left=0;
int right=0;
for(int i=0;i<nums.length;i++){
left=Math.max(left,nums[i]);
right+=nums[i];
}
right++;//因为要左闭右开
while(left<right){
int mid=left+(right-left)/2;
if(f2(nums,mid)<m){
//分的组数太多了
right=mid;
}else if(f2(nums,mid)>m){
//组数分的太少了
left=mid+1;
}else{
//左边界
right=mid;
}
}
return left;
}
private int f2(int[] nums,int x){
//让f(x)返回值是分组数,x为定死的maxSum
int groups=1;
int sum=0;
for(int i=0;i<nums.length;i++){
if(sum+nums[i]<=x){
sum+=nums[i];
}else{
groups++;
sum=nums[i];
}
}
return groups;
}
} |
Beta Was this translation helpful? Give feedback.
-
东哥,有个问题实在想请教,楼上看了一圈没有发现和我有一样问题的,就吃香蕉那题,我之前用Python写的,二分用的是双闭区间没有任何问题。今天用Java刷的时候也是同样的逻辑,但是用双闭区间就出问题了。 class Solution {
public int minEatingSpeed(int[] piles, int h) {
int left = 1;
int right = 1;
for (int pile : piles){
right = Math.max(right, pile);
}
while (left <= right){
int mid = left + (right - left) / 2;
// System.out.print(left + ", " + right + ", " + mid);
// System.out.print(", " + eatingTime(piles, mid) + ", ");
if (eatingTime(piles, mid) > h){left = mid + 1;}
else if (eatingTime(piles, mid) < h){right = mid - 1;}
else if (eatingTime(piles, mid) == h){right = mid - 1;}
// System.out.println(left + ", " + right + ", " + mid);
}
return left;
}
public int eatingTime(int[] piles, int k){
int hours = 0;
for (int i = 0; i < piles.length; i++){
hours += piles[i] / k;
if (piles[i] % k != 0){
hours ++;
}
}
return hours;
}
} 倒在了测试用例piles = [805306368,805306368,805306368], h = 1000000000 class Solution {
public int minEatingSpeed(int[] piles, int h) {
int left = 1;
int right = 1;
for (int pile : piles){
right = Math.max(right, pile);
}
right ++;
while (left < right){
int mid = left + (right - left) / 2;
// System.out.print(left + ", " + right + ", " + mid);
System.out.print(", " + eatingTime(piles, mid) + ", ");
if (eatingTime(piles, mid) > h){left = mid + 1;}
else if (eatingTime(piles, mid) < h){right = mid;}
else if (eatingTime(piles, mid) == h){right = mid;}
// System.out.println(left + ", " + right + ", " + mid);
}
return left;
}
public int eatingTime(int[] piles, int k){
int hours = 0;
for (int i = 0; i < piles.length; i++){
hours += piles[i] / k;
if (piles[i] % k != 0){
hours ++;
}
}
return hours;
}
} |
Beta Was this translation helpful? Give feedback.
-
今天遇到了跟楼上一模一样的问题,Java双闭区间同样的写法几天前还能过今天就不行了,但右开区间没问题,想了很久没明白为什么,持续关注这个问题,希望有大神指教 |
Beta Was this translation helpful? Give feedback.
-
判断搜索方向的函数也可以简单return一个boolean,也减少了前面提到溢出的问题: ·class Solution {
public int minEatingSpeed(int[] piles, int h) {
int left = 1;
int right = 0;
for (int pile : piles) {
right = Math.max(right, pile);
}
int mid = 0;
while(left <= right) {
mid = left + (right - left)/2;
if (canFinish(piles, h, mid)) {
right = mid-1;
} else {
left = mid+1;
}
}
return left;
}
private boolean canFinish(int[] piles, int h, int speed) {
for (int pile : piles) {
h = h - (pile/speed) - (pile%speed > 0 ? 1 : 0);
if (h < 0) {
return false;
}
}
return true;
}
}· |
Beta Was this translation helpful? Give feedback.
-
感觉我有点儿笨比,f(x)的函数变一下就写不出来了 |
Beta Was this translation helpful? Give feedback.
-
这几道题虽然写过,但是第一次用这种思路去看问题,感觉透彻了许多,感谢大佬! |
Beta Was this translation helpful? Give feedback.
-
请教大家一下1011 运输包裹那道题的 private int calculateDays1(int[] weights, int weightCapacity) {
int days = 0, weightSum = 0;
for (int i = 0; i < weights.length;) {
if (weightSum + weights[i] > weightCapacity) {
days++;
weightSum = 0;
continue;
}
weightSum += weights[i++];
}
return days + weightSum > 0 ? 1 : 0;
} 但是怎么都通过不了,debug的时候才发现每次return的时候尽管 private int calculateDays2(int[] weights, int weightCapacity) {
int days = 0, weightSum = 0;
for (int i = 0; i < weights.length;) {
if (weightSum + weights[i] > weightCapacity) {
days++;
weightSum = 0;
continue;
}
weightSum += weights[i++];
}
// return days + weightSum > 0 ? 1 : 0; // didn't add 1 when weightSum > 0
// in return
days += weightSum > 0 ? 1 : 0;
return days;
} 有人遇到过同样的问题或者是知道为什么会这样吗? 测试用例: int[] weights = new int[]{1,2,3,4,5,6,7,8,9,10};
calculateDays1(weights, 32) = 1;
calculateDays2(weights, 32) = 2; |
Beta Was this translation helpful? Give feedback.
-
1011.的f函数这么写也许更容易理解一些 int f(int *weights, int weightsSize, int x)
{
int day_count = 1, weight_count = 0;
for(int i = 0; i < weightsSize; i++)
{
weight_count += weights[i];
if(weight_count > x)
{
day_count++;
weight_count = weights[i];
}
}
return day_count;
} |
Beta Was this translation helpful? Give feedback.
-
东哥,能看一下吗,吃香蕉的那题,如果我用 闭区间 就没法通过,代码如下 public int minEatingSpeed(int[] piles, int h) {
int left = 1;
int right = 1000000000;
int mid = 0;
while (left <= right) {
mid = left + (right - left) / 2;
// 等于
if (getHours(piles, mid) = h) {
right = mid-1;
}
// 小于
else if (getHours(piles, mid) < h) {
right = mid - 1;
}
// 大于
else if (getHours(piles, mid) > h) {
left = mid + 1;
}
}
return left;
}
private int getHours(int[] piles, int x) {
int hours = 0;
for (int i = 0; i < piles.length; i++) {
hours += piles[i] / x;
if (piles[i] % x > 0) {
hours++;
}
}
return hours;
} |
Beta Was this translation helpful? Give feedback.
-
复刷的时候发现了双闭区间的小问题,875. 爱吃香蕉的珂珂中122个用例中最后一个[805306368, 805306368, 805306368]无法通过,上面也有大佬提到了,原因是搜索到left = 1, right = 2时,此时mid = 1,调用getTime函数计算该速度下对应的时间,实际正确时间应该为2415919104但是由于C++中int类型会越界,所以导致该值变为-1879048192,从而导致二分搜索原本应该收缩左边界,变为收缩右边界从而导致出错,具体做法是只需要将辅助函数中统计时间变量和返回的变量改为unsigned int即可解决。下面贴一下自己的代码 class Solution {
public:
unsigned int getTime(vector<int>& piles, unsigned int speed) {
unsigned int time = 0;
for (int i = 0; i < piles.size(); i++) {
time += piles[i] % speed > 0 ? piles[i] / speed + 1 : piles[i] / speed;
}
return time;
}
int minEatingSpeed(vector<int>& piles, int h) {
unsigned int left = 1, right = *max_element(piles.begin(), piles.end());
while (left <= right) {
unsigned int mid = left + (right - left) / 2;
if (getTime(piles, mid) <= h) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
}; |
Beta Was this translation helpful? Give feedback.
-
这篇文章能用支付宝账号付款和绑定吗。我微信扫商店的码,登不进去。 |
Beta Was this translation helpful? Give feedback.
-
不用记住,left 和 right 怎么更新的了,统一更新成mid的方法
|
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
Java实现875. 爱吃香蕉的珂珂 错误原因出在博主原文那个f函数的返回值溢出了int的最大值,除了将f函数的返回值改成long以外,还可以给这个返回值设一个上限,上限为它所比较的那个数(h)的最大值1000000000。
|
Beta Was this translation helpful? Give feedback.
-
20230109 打卡,抽象成单调函数去应用二分搜索也太妙了 |
Beta Was this translation helpful? Give feedback.
-
感谢大佬的文章,现在已经用地很熟练了。 |
Beta Was this translation helpful? Give feedback.
-
var minEatingSpeed = function(piles, h) {
const hit = k => {
let t = 0;
for(let i of piles) t+= Math.ceil(i/k);
return t <= h;
}
let l = 0, r = Math.max(...piles) + 1;
while(l != r - 1){
const mid = Math.floor((l + r)/2);
if(hit(mid)) r = mid;
else l = mid;
}
return r === Math.max(...piles) + 1 ? -1 : r;
}; |
Beta Was this translation helpful? Give feedback.
-
文章链接点这里:二分搜索怎么用?我又总结了套路
评论礼仪 见这里,违者直接拉黑。
Beta Was this translation helpful? Give feedback.
All reactions