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

这篇博客探讨了如何使用二分查找算法解决两个问题:1) 在限定天数内以最低承载力送达包裹;2) 分割数组以使子数组各自和的最大值最小。通过对输入包裹重量和天数的分析,通过二分查找确定最低船载重量,以及分割数组的最优策略,展示了二分查找在解决实际问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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));
    }
}

 

### 使用 MATLAB 实现二分法求解方程 \( x - \tan(x) = 0 \) 为了在 MATLAB 中实现二分法求解方程 \( f(x) = x - \tan(x) = 0 \),需要遵循以下原则: 1. **定义区间**:选择一个初始区间 \([a, b]\),使得 \( f(a) \cdot f(b) < 0 \),即函数在此区间内有变号零点。 2. **设定精度**:指定误差限 \( tol \),当区间的长度满足条件时停止迭代。 3. **编写代码逻辑**:通过不断缩小区间找到近似根。 以下是完整的 MATLAB 实现代码以及说明: #### MATLAB 实现代码 ```matlab function [root, iterations] = bisect_tan() % 定义目标函数 f = @(x) x - tan(x); % 设置初始区间和误差限 a = 4; % 左端点 (可以根据实际情况调整) b = 5; % 右端点 (可以根据实际情况调整) tol = 1e-8; % 设定误差限 % 初始化变 fa = f(a); fb = f(b); max_iter = 100; % 最大迭代次数防止死循环 iterations = 0; % 检查初始区间是否有效 if fa * fb >= 0 error('The initial interval does not bracket the root.'); end while (b - a) / 2 > tol && iterations < max_iter c = (a + b) / 2; % 计算中间点 fc = f(c); % 计算中间点处的函数值 if fc == 0 || abs(fc) < tol break; % 如果达到精度要求,则退出循环 elseif fa * fc < 0 b = c; % 更新右端点 fb = fc; else a = c; % 更新左端点 fa = fc; end iterations = iterations + 1; % 迭代计数器加一 end root = (a + b) / 2; % 返回最终根的位置 end ``` --- #### 关键点解释 1. 函数 `f` 被定义为匿名函数形式,便于调用和修改[^1]。 2. 初始区间的选择非常重要。对于 \( x - \tan(x) = 0 \),由于 \( \tan(x) \) 是周期性的,在某些特定范围内可能存在多个解。因此需合理选取区间以定位感兴趣的根[^2]。 3. 循环终止条件基于两点: - 当前区间的宽度小于给定容忍度 \( tol \)[^3]; - 或者达到了最大允许迭代次数以防无限循环。 --- #### 输出结果示例 运行上述脚本后,将会返回两个主要输出参数: - `root`: 找到的根位置。 - `iterations`: 达到所需精度所需的迭代步数。 例如,如果设置初始区间 `[4, 5]` 和误差限 `1e-8`,可能得到如下结果: ```plaintext root = 4.49340945790906 iterations = 25 ``` 这表明该方法成功找到了位于约 \( x = 4.4934 \) 处的一个根,并经过了 25 步迭代完成计算。 --- ### 注意事项 - 对于 \( x - \tan(x) = 0 \),\( \tan(x) \) 存在奇异性(垂直渐近线),所以在靠近这些奇异点附近可能会遇到数值不稳定的情况。应小心避开这些区域。 - 若无法预先知道合适的初始区间范围,可以通过绘制图像辅助判断潜在根的大致分布情况。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值