华为OD机试 - 猴子吃桃 - 二分查找(Java 2025 A卷 100分)

本文介绍了华为OD机试中的一道题目——猴子吃桃问题,利用二分查找法寻找孙悟空在天兵天将回来前吃完所有蟠桃的最小速度。详细阐述了解题思路,包括从回溯法到二分查找法的优化,并给出了Java算法源码示例。

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

一、题目描述

孙悟空喜欢吃蟠桃,一天他乘守卫蟠桃园的天兵天将离开了而偷偷的来到王母娘娘的蟠桃园偷吃蟠桃。

已知蟠桃园有 N 棵蟠桃树,第 i 棵蟠桃树上有 N[i](大于 0)个蟠桃,天兵天将将在 H(不小于蟠桃树棵数)小时后回来。

孙悟空可以决定他吃蟠桃的速度 K(单位:个/小时),每个小时他会选择一颗蟠桃树,从中吃掉 K 个蟠桃,如果这棵树上的蟠桃数小于 K,他将吃掉这棵树上所有蟠桃,然后这一小时内不再吃其余蟠桃树上的蟠桃。

孙悟空喜欢慢慢吃,但仍想在天兵天将回来前将所有蟠桃吃完。

求孙悟空可以在 H 小时内吃掉所有蟠桃的最小速度 K(K 为整数)。

二、输入描述

从标准输入中读取一行数字,前面数字表示每棵数上蟠桃个数,最后的数字表示天兵天将将离开的时间。

三、输出描述

吃掉所有蟠桃的 最小速度 K(K 为整数)或 输入异常时输出 -1。

1、输入

3 11 6 7 8

2、输出

4

3、说明

天兵天将8个小时后回来,孙悟空吃掉所有蟠桃的最小速度4。

  1. 第1小时全部吃完第一棵树,吃3个,
  2. 第2小时吃4个,第二棵树剩7个,
  3. 第3小时吃4个,第二棵树剩3个,
  4. 第4小时吃3个,第二棵树吃完,
  5. 第5小时吃4个,第三棵树剩2个,
  6. 第6小时吃2个,第三棵树吃完,
  7. 第7小时吃4个,第4棵树剩3个,
  8. 第8小时吃3个,第4棵树吃完。

四、解题思路

题目描述有点复杂,多读几遍不难理解,意思就是:

一个小时只能在一棵桃树上吃,如果吃不完,下一个小时继续吃,如果吃完了,就不吃了,但不能去吃下一颗桃树,要守规矩。

  1. 输入几个数,表示每颗树的桃子数量;
  2. 在H个小时内,以最慢的速度将这些桃子全部吃完。

很明显的回溯问题,一个一个找呗,看哪个速度最合适。

五、Java算法源码

1、从速度1开始回溯,寻找H小时内吃完所有桃子的最小速度

假如孙猴子一小时吃100个桃子最合适,效率有点堪忧。

public class Test06 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int[] arr = Arrays.stream(sc.nextLine().split(" ")).mapToInt(Integer::parseInt).toArray();
        // 天兵天将将离开的时间H
        int H = arr[arr.length - 1];

        // 获取每棵数上蟠桃个数
        arr = Arrays.copyOf(arr, arr.length - 1);

        // 从少到多排序
        Arrays.sort(arr);

        // 从速度1开始回溯,寻找H小时内吃完所有桃子的最小速度
        dfs(arr, H, 1);

        System.out.println(minSpeed);
        System.out.println("执行次数:" + num);
    }

    // 记录回溯次数,优化回溯算法
    static int num = 0;
    // 吃完所有桃子的最小速度
    static int minSpeed = 0;

    /**
     * 从速度1开始回溯,寻找H小时内吃完所有桃子的最小速度
     */
    private static void dfs(int[] arr, int H, int K) {
        int times = 0;
        for (int i = 0; i < arr.length; i++) {
            num++;
            // 以速度K吃完每颗桃树的时间
            times += arr[i] / K;
            if (arr[i] % K != 0) {
                times++;
            }
        }

        // 因为速度从小到大递增,当时间 <= H时,即最小速度
        if (times <= H) {
            minSpeed = K;
            return;
        } else {
            // 吃不完,加快速度
            dfs(arr, H, ++K);
        }
    }
}

输入:3 25 6 7 8
输出:7
执行次数:28

2、预测一个最可能的速度

所有桃子的数量除以总时间,得出每小时吃桃子数量,我觉得这个就是比较接近最小速度的最优解。

  1. 如果吃不完,就加快速度;
  2. 如果吃完了,就吃慢一点;
  3. 哈哈
public class Test08 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int[] arr = Arrays.stream(sc.nextLine().split(" ")).mapToInt(Integer::parseInt).toArray();
        // 天兵天将将离开的时间H
        int H = arr[arr.length - 1];

        // 获取每棵数上蟠桃个数
        arr = Arrays.copyOf(arr, arr.length - 1);
        // 从少到多排序
        Arrays.sort(arr);

        // 预测最可能速度,优化回溯算法
        int sum = Arrays.stream(arr).sum();
        int possible = sum % H == 0 ? sum / H : sum / H + 1;

        System.out.println("预测最可能速度:" + possible);

        // 从最可能速度开始回溯,寻找H小时内吃完所有桃子的最小速度
        dfs(arr, H, possible);

        System.out.println(minSpeed);
        System.out.println("执行次数:" + num);
    }

    // 记录回溯次数,优化回溯算法
    static int num = 0;
    // 吃完所有桃子的最小速度
    static int minSpeed = 0;
    // 上一次吃完所有桃子的速度
    static int preSpeed = 0;

    private static void dfs(int[] arr, int H, int K) {
        int times = 0;
        for (int i = 0; i < arr.length; i++) {
            num++;
            // 以速度K吃完每颗桃树的时间
            times += arr[i] / K;
            if (arr[i] % K != 0) {
                times++;
            }
        }

        // 吃不完,加快速度
        if (times > H) {
            // 如果上次已经吃完,则上次吃完的速度即最小速度
            if (preSpeed != 0) {
                minSpeed = preSpeed;
                return;
            }
            dfs(arr, H, ++K);
        } else if (times < H) {// 吃完了,再吃慢一点
            preSpeed = K;
            dfs(arr, H, --K);
        } else {// 刚好吃完,返回最小速度
            minSpeed = K;
            return;
        }
    }
}

输入:3 25 6 7 8
输出:7
预测最可能速度:6
执行次数:12

3、一切都靠猜 + 回溯,有点太小儿科了,通过二分查找正统一下

public class Test09 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int[] arr = Arrays.stream(sc.nextLine().split(" ")).mapToInt(Integer::parseInt).toArray();
        // 天兵天将将离开的时间H
        int H = arr[arr.length - 1];

        // 获取每棵数上蟠桃个数
        arr = Arrays.copyOf(arr, arr.length - 1);
        // 从少到多排序
        Arrays.sort(arr);

        int left = 1;
        int right = arr[arr.length - 1];
        while (left < right) {
            int mid = (left + right) / 2;
            // 吃完了,还能慢一点
            if (dfs(arr, H, mid)) {
                right = mid;
            } else {// 没吃完,吃快一点
                left = mid + 1;
            }
        }
        System.out.println(left);
        System.out.println("执行次数:" + num);
    }

    // 记录回溯次数,优化回溯算法
    static int num = 0;
    // 上一次吃完所有桃子的速度
    static int preSpeed = 0;

    private static boolean dfs(int[] arr, int H, int K) {
        int times = 0;
        for (int i = 0; i < arr.length; i++) {
            num++;
            // 以速度K吃完每颗桃树的时间
            times += arr[i] / K;
            if (arr[i] % K != 0) {
                times++;
            }
        }
        return times <= H;
    }
}

输入:3 25 6 7 8
输出:7
执行次数:16

通过对比,还是预测一个最可能的速度效率最高,嘿嘿~

六、效果展示

1、输入

3 25 6 7 8

2、输出

7

3、说明

在这里插入图片描述

Canvas是HTML5中用于绘图的API,你可以通过JavaScript操作它来绘制各种形状,包括图形化的子。下面是一个简单的步骤,展示如何使用Canvas在Web上画出一个基本的子: 1. 首先,创建一个HTML `<canvas>` 元素,并设置其id,例如 `myCanvas`。 ```html <canvas id="myCanvas" width="400" height="400"></canvas> ``` 2. 使用JavaScript获取Canvas元素并获取它的2D渲染上下文 `ctx`。 ```javascript var canvas = document.getElementById('myCanvas'); var ctx = canvas.getContext('2d'); ``` 3. 定义子的基本形状,比如椭圆(代表子主体),以及两个小圆(代表核)。你需要计算好半径、位置等参数。 ```javascript // 子主体 var bodyX = canvas.width / 2; var bodyY = canvas.height / 2; var bodyWidth = 200; var bodyHeight = 250; var bodyColor = '#FFA54F'; // 核 var kernelX = bodyX + bodyWidth * 0.8; var kernelY = bodyY + bodyHeight * 0.6; var kernelRadius = 10; var kernelColor = '#8B4513'; ``` 4. 绘制子主体和核。 ```javascript ctx.beginPath(); ctx.save(); // 保存当前状态 // 主体 ctx.fillStyle = bodyColor; ctx.arc(bodyX, bodyY, Math.min(bodyWidth, bodyHeight) / 2, 0, Math.PI * 2); ctx.fill(); // 核心 ctx.beginPath(); ctx.fillStyle = kernelColor; ctx.arc(kernelX, kernelY, kernelRadius, 0, Math.PI * 2); ctx.fill(); ctx.restore(); // 恢复到之前的状态 ``` 5. 可选地,你可以添加阴影效果或者纹理,增加真实感。最后记得清除画布背景颜色。 ```javascript // 添加阴影 ctx.shadowBlur = 10; // 阴影模糊程度 ctx.shadowOffsetX = 5; ctx.shadowOffsetY = 5; ctx.fillStyle = 'white'; // 清除背景 ctx.fillRect(0, 0, canvas.width, canvas.height); // 或者,如果你想要更复杂的形状,可以使用path方法和stroke()、fill()等函数绘制。 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哪 吒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值