二分法的使用LeetCode1011送包裹,875吃香蕉

LeetCode1011.https://leetcode-cn.com/problems/capacity-to-ship-packages-within-d-days/

暴力解法:

复杂写法,一次一次尝试,超时!!!

// // 超时
// class Solution {
//     int[] sumN;
//     public int shipWithinDays(int[] weights, int D) {
//         // 前N个货物的总重量
//         sumN = new int[weights.length];
//         int curSum=0;
//         for (int i = 0; i < weights.length; i++) {
//             sumN[i] =curSum+weights[i];
//             // System.out.println(sumN[i]);
//             curSum += weights[i];
//         }
//         // 边界条件 
//         if(D==1){
//             return sumN[sumN.length-1];
//         }
//         sol(weights,0,0,0,D);

//         return bestValue;
//     }
//     int bestValue = Integer.MAX_VALUE;
//     /**
//         * 有两种放法,自己单独放一堆,或者与前面的放一在堆
//         * @param weights
//         * @param curIndex
//         * @param lastSum 上一个堆的和
//         * @param maxSum 前面所有堆的最大值
//         * @param D 剩余堆数
//         * @return
//         */
//     public void sol(int[] weights,int curIndex,int lastSum,int maxSum, int D){
//         // D为1则剩余所有放一堆(注!!!有可能D为一堆的时候可以将当前与之前的合并!!而不是往后放!!)
//         // 应该改为D==0 的时候return D等于1 不需要return!!!

//         if(D==0){
//             return;
//         }
//         //(注!!!在没有return的条件下,不要修改形参中的值) 该语句不能省,当堆分配完必须计算后续结果,但不能return由于后面的可能会分配到已有的堆中。
//         if(D==1){
//             // 将curIndex和以后的堆放一起的情况
//             int temp = Math.max(maxSum,sumN[sumN.length-1]-sumN[curIndex-1]);
//             bestValue = Math.min(bestValue,temp);
//         }
//         if(curIndex  == weights.length ){
//             bestValue = Math.min(bestValue,maxSum);
//             return ;
//         }
//         // 当剩下的数刚好只剩D那么每一个数放一堆(注!!!在没有return的条件下,不要修改形参中的值)
//         if(weights.length-curIndex == D){
//             // 比较之前的堆,与剩余堆的最大值
//             for(int i = curIndex; i<weights.length; i++){
//                 maxSum = Math.max(maxSum,weights[i]);
//             }
//             bestValue = Math.min(bestValue,maxSum);
//             return;
//         }

//         // 自己放一堆
//         sol(weights,curIndex+1,weights[curIndex],Math.max(weights[curIndex],maxSum),D-1);

//         // 和上一组放一堆(排除上一组没有的情况:第一次放)
//         if(lastSum>0) {
//             sol(weights, curIndex + 1, weights[curIndex] + lastSum, Math.max(weights[curIndex] + lastSum, maxSum), D);
//         }
//     }
// }

看了解答:二分法

正确解:

class Solution {
    // 利用二分法,找到承载能力,判断该值是否可以运完所有的货
    // 承载能力一定是在第一个货物的重量和所有货物重量之间的(存在问题)
    // 注!!!必须是最大的一个货物的重量开始,否则中间会有装不下的!!!

    public int shipWithinDays(int[] weights, int D) {
        // 前N个货物的总重量
        int[] sumN = new int[weights.length];
        int curSum=0;
        // 最大货物重量
        int maxSize=0;
        for (int i = 0; i < weights.length; i++) {
            maxSize = Math.max(maxSize,weights[i]);
            sumN[i] =curSum+weights[i];
            curSum += weights[i];
        }
        
        return midSearch(maxSize, curSum, sumN, D);
    }
    public int midSearch(int start, int end, int[] sumN, int D){
        if(start == end){
            return start;
        }
        // 找到当前中间承载量
        int mid = (start + end)/2;
        // System.out.println("尝试装载:"+mid);
        // 判断当前中间承载量能否满足
        if(canLoad(mid, D, sumN)){
            // 往左边找,因为右边一定可以承载
            return midSearch(start, mid, sumN, D);
        }else{
            // 不能承载,说明在右边
            return midSearch(mid+1, end, sumN, D);
        }
    }
    // 判断是否可以承载
    // 贪心
    public boolean canLoad(int load, int D, int[] sumN){
        // 上一次装的重量
        int lastload=0;
        // 装了几次,注!!!从1开始因为装不下当前值的时候,说明前面已经装了一次,本次开始是第二次!!!
        int times=1;
        // System.out.println("*****当前装载******"+load);
        for(int i=0; i<sumN.length; i++){
            // 下面成立表示当前货物装不下
            if(sumN[i] - lastload > load){        
                lastload = sumN[i-1];
                times++;
                // System.out.println("当前装不下,上次装载量:"+lastload+",当前装载次数:"+times);
            }      
            if(times>D){
                return false;
            }
        }
        // System.out.println("装载成功使用次数"+times);
        return true;
    }

}

看到类似题:

Leetcode875

class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        // 类似Q1011送包裹问题二分法解决
        int maxPile=0;
        for(int i=0; i<piles.length; i++){
            maxPile = Math.max(maxPile,piles[i]);
        }

        return find(1, maxPile, piles, h);

    }
    // 在最小速度和最大速度之间
    public int find(int start, int end, int[] piles,int h){
        if(start == end){
            return start;
        }
        // 当前速度(取中值)
        int mid = (start+end)/2;
        System.out.println("当前速度:"+mid);
        if(canFinish(mid ,h ,piles)){
            return find(start, mid, piles, h);
        }else{
            return find(mid+1, end, piles, h);
        }
    }
    // 是否可以以当前速度完成
    public boolean canFinish(int V, int h, int[] piles){
        int times=0;
        for(int i=0; i<piles.length; i++){
            // times += (piles[i]/V + 1); 注!!!,当piles刚好为V时吃了两次,因此错误。所以使用向右取整数
            times += Math.ceil((float)piles[i]/V);
        }
        // 使用次数大于h表示吃不完
        if(times > h){
            System.out.println("只能吃"+h+"次,吃了"+times+"次,没吃完");
            return false;
        }
        System.out.println("只能吃"+h+"次,吃了"+times+"次,吃完了");
        return true;
    }
}

二分法思路:

1.取值有一定的范围,从可能的最小的范围和最大的范围每次取中间值逼近。

就如第一题送货,最小量是最大货物的重量,最大量是所有的货物重量之和。

第二题中,最小量是1(每次吃1根香蕉),最大量是所有堆的最大值(一次只能吃一堆)。

2.取中间值mid=(start+end)/2.

3.判断中间值的情况:

中间值如果可以满足,那么最后的结果一定在左边或者中间位置,那么递归调用solution(start,mid)。

相反就是solution(mid+1,end)。

原文地址:https://www.cnblogs.com/wsZzz1997/p/14709494.html