使用二分法--比较有趣的几道题

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

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

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

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

示例 1:

输入:weights = [1,2,3,4,5,6,7,8,9,10], D = 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], D = 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 <= D <= weights.length <= 50000
    1 <= weights[i] <= 500


/**
    思路:
         对于一艘承载力为K船来说,我们必然会在不超过其承载力的前提下贪心地往上装载货物,
         这样才能使得运送包裹所花费的时间最短。
    如果船在承载力为K的条件下可以完成在D天内送达包裹的任务,那么任何承载力大于K的条件下依然也能完成任务。
    我们可以让这个承载力K从max(weights)开始(即所有包裹中质量最大包裹的重量,
    低于这个重量我们不可能完成任务),逐渐增大承载力K,直到K可以让我们在D天内送达包裹。
    此时K即为我们所要求的最低承载力。
    逐渐增大承载力K的方法效率过低,让我们用二分查找的方法来优化它。

    算法:
    定义函数canShip(D,K)来判断在最低承载力为K的情形下能否在D天内送达所有包裹。
    我们所要做的就是按照传送带上货物的顺序,依次且尽可能多地往船上装载货物,当该艘船无法装下更多货物时,我们换一搜船,同时将天数加111。当运输完所有货物后,我们判断所用的天数是否小于等于DDD。
    用二分查找的方式,来查找这个最低承载力,如果mid可以完成任务,
    我们把查找范围缩减至[lo,mid],注意不是mid+1,因为mid可能是我们所求的解),
    否则我们去[mid+1,hi]区间中继续查找,详情见代码。
     */

public static int shipWithinDays(int[] weights, int D) {
        int n=weights.length;
        int sum=0,max=0;
        for (int i = 0; i < n; i++){
            sum+=weights[i];
            max=max>weights[i]?max:weights[i];
        }

        int left=max,right=sum;
        while (left<right){
            int mid=left+(right-left)/2;
            if (canShip(weights,D,mid)){  //判断在最低承载力为K的情形下能否在D天内送达所有包裹。
                right=mid;
            }else
                left=mid+1;
        }

        return left;
    }

    private static boolean canShip(int[] weights, int D, int K) {
        int cur=K;
        for (int i = 0; i < weights.length; i++) {
            if (weights[i]>K)   return false;
            if (cur<weights[i]){
                cur=K;
                D--;
            }
            cur-=weights[i];
        }
        return D>0;  //能否在D天内送达包裹
    }

    public static void main(String[] args) {
        int[] weights = {1,2,3,4,5,6,7,8,9,10};
        System.out.println(shipWithinDays(weights, 5));
    }
}

 

410. 分割数组的最大值

给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。

注意:
数组长度 n 满足以下条件:

    1 ≤ n ≤ 1000
    1 ≤ m ≤ min(50, n)

示例:

输入:
nums = [7,2,5,10,8]
m = 2

输出:
18

解释:
一共有四种方法将nums分割为2个子数组。
其中最好的方式是将其分为[7,2,5] 和 [10,8],
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。

public class Num410 {
    public static int splitArray(int[] nums, int m) {
        int n=nums.length;
        int sum=0,max=0;
        for (int i = 0; i < n; i++) {
            sum+=nums[i];
            max=max>nums[i]?max:nums[i];
        }

        int low=max,high=sum;  //数组各自和的最大值:
                               //最小的可能性就是子数组只有一个数(该数组的最大值),
                               //最大的可能性就是子数组里有全部的数(数组和即为最大值)
        while (low<high){
            int mid=low+(high-low)/2;  //子数组各自和的最大值为Mid
            if (judge(nums,mid,m)){  //判断当最大值为Mid时,是否能分成m个子数组
                high=mid;
            }else
                low=mid+1;

        }
        return low;
    }

    private static boolean judge(int[] nums, int K, int m) {
        int cur=K;
        for (int num : nums) {
            if (cur<num){
                m--;
                cur=K;
            }
            cur-=num;
        }
        return m>0;
    }

    public static void main(String[] args) {
        int[] nums = {7,2,5,10,8};
        System.out.println(splitArray(nums, 2));
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值