二分搜索问题泛化

什么问题可以用二分搜索?所有可以抽象出如下信息的题目都可以采用二分搜索:

  • 抽象出一个自变量x,一个关于x的单调函数f(x)
  • 题目需要求f(x)==target时x的最小值或者最大值

410. 分割数组的最大值

题目:给定一个非负整数数组 nums 和一个整数 m ,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。
分析:x表示子数组和的最大值,f(x)表示最大值为x的情况下子数组的个数(单调递减),求最小值
代码:

class Solution {
    public int splitArray(int[] nums, int m) {
        int max = 0;
        int sum = 0;

        for (int num : nums){
            max = Math.max(max, num);
            sum += num;
        }

        int left = max; 
        int right = sum;
        while (left <= right){
            int mid = left + (right - left)/2;
            int splits = split(nums, mid);
            if (splits > m){
                left = mid + 1;
            }else if (splits < m){
                right = mid - 1;
            }else if (splits == m){
                right = mid - 1;
            }
        }
        return left;
    }

    public int split(int[] nums, int maxSum){
        int splits = 1;
        int curSum = 0;
        for (int num : nums){
            if (curSum + num > maxSum){
                curSum = 0;
                splits++;
            }
            curSum += num;
        }
        return splits;
    }
}

875. 爱吃香蕉的珂珂

题目:珂珂喜欢吃香蕉。这里有n堆香蕉,第i堆中有piles[i]根香蕉。警卫已经离开了,将在h小时后回来。珂珂可以决定她吃香蕉的速度k(单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉k根。如果这堆香蕉少于k根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。返回她可以在h小时内吃掉所有香蕉的最小速度k(k为整数)。
分析:x表示吃香蕉的速度,f(x)表示吃完需要的时间(单调递减),求最小值
代码:

class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        int max = 0;
        for (int pile : piles){
            max = Math.max(max, pile);
        }

        int left = 1;
        int right = max;
        while (left < right){
            int mid = left + (right - left)/2;
            int count = calculateSum(piles, mid);
            if (count < h){
                right = mid;
            }else if (count > h){
                left = mid + 1;
            }else{
                right = mid;
            }
        }
        return left;
    }

    public int calculateSum(int[] nums, int speed){
        int time = 0;
        for (int num : nums){
            if (num % speed == 0){
                time += num/speed;
            }else{
                time += num/speed + 1;
            }
        }
        return time;
    }
}

LCP 12. 小张刷题计划

题目:为了提高自己的代码能力,小张制定了 LeetCode 刷题计划,他选中了 LeetCode 题库中的 n 道题,编号从 0 到 n-1,并计划在 m 天内按照题目编号顺序刷完所有的题目(注意,小张不能用多天完成同一题)。在小张刷题计划中,小张需要用 time[i] 的时间完成编号 i 的题目。此外,小张还可以使用场外求助功能,通过询问他的好朋友小杨题目的解法,可以省去该题的做题时间。为了防止“小张刷题计划”变成“小杨刷题计划”,小张每天最多使用一次求助。我们定义 m 天中做题时间最多的一天耗时为 T(小杨完成的题目不计入做题总时间)。请你帮小张求出最小的 T是多少。
分析:x表示每天刷题的时间,f(x)刷完需要的天数(单调递减),求最小值
代码:

class Solution {
    public int minTime(int[] time, int m) {
        int left = 0;
        int right = Integer.MAX_VALUE;
        while (left <= right){
            int mid = left + (right - left)/2;
            int day = calcuTime(time, mid);
            if (day <= m){
                right = mid - 1;
            }else{
                left = mid + 1;
            }
        }
        return left;
    }

    public int calcuTime(int[] time, int maxTime){
        int day = 1;
        boolean help = true;
        int curTime = 0;
        int max = 0;
        for (int i = 0; i < time.length; i++){
            max = Math.max(max, time[i]);
            curTime += time[i];
            if (curTime > maxTime){
                if (help){
                    curTime -= max;
                    help = false;
                }else{
                    day++;
                    help = true;
                    max = 0;
                    curTime = 0;
                    i--;
                }
            }
        }
        return day;
    }
}

1011. 在 D 天内送达包裹的能力

题目:传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。返回能在 days 天内将传送带上的所有包裹送达的船的最低运载能力。
分析:x表示每天的运载量,f(x)表示运完需要的天数(单调递减),求最小值
代码:

class Solution {
    public int shipWithinDays(int[] weights, int days) {
        int max = 0;
        int sum = 0;
        for (int weight : weights){
            max = Math.max(max, weight);
            sum += weight;
        }

        int left = max;
        int right = sum;
        while (left <= right){
            int mid = left + (right - left)/2;
            int num = calcuDay(weights, mid);
            if (num < days){
                right = mid - 1;
            }else if (num > days){
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }
        return left;
    }

    public int calcuDay(int[] weights, int maxNum){
        int num = 1;
        int curNum = 0;
        for (int weight : weights){
            if (curNum + weight > maxNum){
                num++;
                curNum = 0;
            }
            curNum += weight;
        }
        return num;
    }
}

1482. 制作 m 束花所需的最少天数

题目:给你一个整数数组 bloomDay,以及两个整数 m 和 k 。现需要制作 m 束花。制作花束时,需要使用花园中 相邻的 k 朵花 。花园中有 n 朵花,第 i 朵花会在 bloomDay[i] 时盛开,恰好可以用于一束花中。请你返回从花园中摘 m 束花需要等待的最少的天数。如果不能摘到 m 束花则返回 -1 。
分析:x表示限制花开的天数,f(x)表示在x天的条件下能够制作的花束的数量(单调递增),求最小值
代码:

class Solution {
    public int minDays(int[] bloomDay, int m, int k) {
        if (bloomDay.length < m * k){
            return -1;
        }

        int min = 0;
        int max = 0;
        for (int b : bloomDay){
            min = Math.min(min, b);
            max = Math.max(max, b);
        }

        int left = min;
        int right = max;
        while (left < right){
            int mid = left + (right - left)/2;
            int count = calcuFlower(bloomDay, mid, m, k);
            if (count >= m){
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        return left;
    }

    public int calcuFlower(int[] days, int maxDay, int m, int k){
        int flower = 0;
        int num = 0;
        for (int day : days){
            if (day > maxDay){
                flower = 0;
            }else{
                flower++;
                if (flower == k){
                    num++;
                    flower = 0;
                }
            }
        }
        return num;
    }
}

1552. 两球之间的磁力

题目:在代号为 C-137 的地球上,Rick 发现如果他将两个球放在他新发明的篮子里,它们之间会形成特殊形式的磁力。Rick 有 n 个空的篮子,第 i 个篮子的位置在 position[i] ,Morty 想把 m 个球放到这些篮子里,使得任意两球间 最小磁力最大。已知两个球如果分别位于 x 和 y ,那么它们之间的磁力为 |x - y| 。给你一个整数数组 position 和一个整数 m ,请你返回最大化的最小磁力。
分析:x表示两球间的最小磁力,f(x)表示在x的条件下能够放多少个球(单调递减),求最大值
代码:

class Solution {
    public int maxDistance(int[] position, int m) {
        Arrays.sort(position);
        int len = position.length;
        int left = 1;
        int right = position[len-1];

        while (left < right){
            int mid = left + (right - left)/2;
            int numBall = calcuBall(position, mid);
            if (numBall < m){
                right = mid;
            }else if (numBall >= m){
                left = mid + 1;
            }
        }
        return left - 1;
    }

    public int calcuBall(int[] position, int maxPower){
        int num = 1;
        int start = 0;
        for (int i = 1; i < position.length; i++){
            if (position[i] - position[start] >= maxPower){
                num++;
                start = i;
            }
        }
        return num;
    }
}
posted @   学海无涯#  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示