一类二分法的经典例题

二分法的经典情况

题目介绍

传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。

传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。

返回能在 days 天内将传送带上的所有包裹送达的船的最低运载能力。

示例 1:

输入:weights = [1,2,3,4,5,6,7,8,9,10], days = 5
输出:15
解释:
船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
第 1 天:1, 2, 3, 4, 5
第 2 天:6, 7
第 3 天:8
第 4 天:9
第 5 天:10

请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。 

示例 2:

输入:weights = [3,2,2,4,1,4], days = 3
输出:6
解释:
船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示:
第 1 天:3, 2
第 2 天:2, 4
第 3 天:1, 4

示例 3:

输入:weights = [1,2,3,1,1], D = 4
输出:3
解释:
第 1 天:1
第 2 天:2
第 3 天:3
第 4 天:1, 1

提示:

  • 1 <= days <= weights.length <= 5 * 104
  • 1 <= weights[i] <= 500

题目解析

容易想偏的dp思路

小提醒

如果仅想学习二分法的应用和了解经典情景的话,可以跳过这一部分

为什么要插入这样的dp思路分析呢,因为我动态规划往往是解题的利器,能用动态规划考虑的一般都很不错,但是本题给了我一个提醒,那就是重要的是时间复杂度分析,判读一个算法的好坏与可用性不是用了多么酷的方法,而是时间复杂度是否合适

另一方面,既然不是用dp思路的题反而用dp思路想出来,可见过程也是比较复杂的,也可以作为一种锻炼

dp思路解析

受到背包问题采用动态规划思路的影响,该题很容易也想成动态规划

而且我想到的状态转移方程为

创建dp数组,来存放运送下标个数货物要付出的代价,然后从1天,2天考虑到D天,不断更新dp数组

那么状态转移方程就是,假如我们第n+1天运送m个货物,那么n+1会把m个货物分割为两部分,m1,m2

m1是又是经过n天的划分,其最好的结果已经保留在我们dp数组中,而m2仅需要把货物累加起来就是第n+1天付出的代价了

上述只是m个货物,第n+1天的一种情况,我们要把m分割成如下

货物m1货物m2n+1天m货物最小运输代价
01-mMath.max(dp[0],sum(cost[1]-cost[m]))
0-12-mMath.max(dp[1],sum(cost[2]-cost[m]))
0-23-mMath.max(dp[2],sum(cost[3]-cost[m]))
0-(m-2)(m-1)-mMath.max(dp[m-2],sum(cost[m-1]-cost[m]))

最后n+1天m货物最小运输代价 要取第三列的最小值。

image.png

dp code
class Solution {
    public int shipWithinDays(int[] weights, int days) {
        //如果天数太多,就取决于货物最重的那一个
        if(days>=weights.length){
            Arrays.sort(weights);
            return weights[weights.length-1];
        }
        int[] array=weights;
        //利用差分,避免每次求和 array[j]-array[i]=x从i到j array[x]的和
        for (int i = 1; i < array.length; i++) {
            array[i]+=array[i-1];
        }
        int[] dp=array;
        for (int i = 1; i < days; i++) {
            System.out.println(Arrays.toString(dp));
            int[] dp_new=new int[weights.length];
            for (int j = 0; j < dp_new.length; j++) {
                for (int k = 0; k < j; k++) {
                    int temp=Math.max(dp[k],array[j]-array[k]);
                    if(dp_new[j]!=0){
                        dp_new[j]=Math.min(temp,dp_new[j]);
                    }else{
                        dp_new[j]=temp;
                    }
                    
                }   
            }
            dp=dp_new;
        }
        System.out.println(Arrays.toString(dp));
        return dp[dp.length-1];
    }
}
测试用例以及分析
[1,2,3,4,5,6,7,8,9,10]
5


[1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
[0, 2, 3, 6, 9, 11, 15, 21, 24, 28]
[0, 2, 3, 4, 6, 9, 11, 15, 17, 21]
[0, 2, 3, 4, 5, 6, 9, 11, 15, 17]
[0, 2, 3, 4, 5, 6, 7, 9, 11, 15]
15

通过上述代码,可以输出打赢这样的一个二维数组,可以很骄傲的说第i行和第j列代表i+1天运输j个货物花费的最小代价。

存在的缺点

通过好几个for循环也可以看出,这是一个时间复杂度爆炸的算法,幸亏还是用了点dp思想,不然就好像是暴力遍历一般 (甚至有时候能想出一个暴力遍历就很难)

设为n天,货物数组长度为m,简单分析一下时间复杂度为
(n * m * m)

二分法

怎么使用二分法呢

其实思路很简单,就是最小运送货物能力也要大于等于最重的一个货物,设此为left

最大运送货物的能力肯定小于货物重量的总和,设为right

因此我们二分收缩找个区域即可,如果mid满足,就让right降低,反过来提高

如果满足我们

code
class Solution {
    public int shipWithinDays(int[] weights, int days) {
        int sum=0;
        int max=0;
        for (int i = 0; i < weights.length; i++) {
            sum+=weights[i];
            max=Math.max(weights[i],max);
        }
        if(days>=weights.length)return max;
        int left=Math.max(sum/days,max);
        int right=sum;
        while (right>=left){
            int mid=(right+left)/2;
            if(canFinish(mid,weights,days)){
                right=mid-1;
            }else {
                left=mid+1;
            }
        }
        return left;
    }
    public boolean canFinish(int val, int[]weights, int days){
        int temp=0;
        for (int i = 0; i < weights.length; i++) {
            if(temp+weights[i]<=val){
                temp+=weights[i];
            }else {
                temp=weights[i];
                days--;
                if(days<=0)return false;
            }
        }
        return true;
    }
}

时间复杂度为 nlongΣw

Σw 是数组weights 中元素的和

n为weights数组的长度

leecode传送门

以下是本题以及两道相似思路的题目:

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

https://leetcode-cn.com/problems/xiao-zhang-shua-ti-ji-hua/

https://leetcode-cn.com/problems/koko-eating-bananas/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值