Bootstrap

【贪心算法】绿洲之旅:最少次数补给探索

问题背景

假设一位旅行者需要穿越一片沙漠,起点到终点的距离为 D 公里,旅行者初始携带了 W 升水,每前进一公里需要消耗一升水。在穿越过程中,沿途会经过 N 个补给站,补给站的位置和可提供的水量分别由数组 positionsupply 描述。

旅行者的目标是用最少的补给次数到达终点。如果在某个时刻发现无法前行,则无法完成任务,输出 -1。

在这一问题中,我们需要兼顾两个核心点:

  1. 每次补给的时机:选择适当的时机进行补给,以保证旅程能够继续。
  2. 补给的优先级:从多个补给选项中,选择能让旅程覆盖最远距离的补给量。

解决思路

从问题描述来看,它具备明显的贪心特性:我们需要在每个关键点(当前位置无水前行时)做出最优决策,尽量选择当前能提供最多水量的补给站,以减少未来补给次数。具体的解决思路如下:

  1. 按距离排序补给站
    补给站的位置不一定是按顺序给出的,因此需要先将它们按距离从小到大排序。这样可以保证旅行者在沿途遇到补给站的顺序与实际前进顺序一致。
  2. 维护一个最大堆记录可选补给量
    在每经过一个补给站时,将其可提供的水量存入最大堆中。当发现当前携带的水量不足以到达下一个目的地时,从堆中取出最大值进行补给。这种策略确保了当前的补给选择是对后续行程最有利的。
  3. 处理终点作为特殊情况
    在所有补给站处理完后,还需要考虑终点的情况。如果当前水量仍不足以到达终点,则需要继续从最大堆中补给,否则任务失败。
  4. 计算补给次数
    每次从堆中取水,即为一次补给。通过计数器记录补给次数,最终返回结果。

贪心算法的优势

这一问题选择贪心算法有以下几个优势:

  1. 局部最优解的有效性
    在每次面临“是否需要补给”的选择时,选用当前能提供最大补给量的站点,总能为未来节省更多水量需求,减少补给次数。
  2. 时间效率高
    贪心算法主要依赖排序和堆操作,时间复杂度为 O(nlog⁡n),非常适合处理大规模输入。
  3. 简单易实现
    贪心算法直接针对问题的局部最优策略构建解决方案,逻辑清晰,代码量少。

实现步骤详解

具体的实现可以分为以下几个步骤:

  1. 输入数据的预处理
    将补给站信息存储为一个二维数组,并按照距离进行排序。

  2. 使用最大堆记录补给量
    遍历每个补给站时,将其补给量加入最大堆。在水量不足时,从堆中提取最大补给量。

  3. 终点检查
    当所有补给站处理完后,仍需检查剩余水量是否能覆盖到终点。如果无法到达,则返回 -1。

  4. 补给次数统计
    每次补充水量,计数器加一,最后输出最少补给次数。

import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;

public class travel_lvzhou {

        public static int solution(int d, int w, int[] position, int[] supply) {
            int n = position.length;
            // 补给站按距离排序
            int[][] stations = new int[n][2];
            for (int i = 0; i < n; i++) {
                stations[i][0] = position[i];
                stations[i][1] = supply[i];
            }
            Arrays.sort(stations, Comparator.comparingInt(a -> a[0]));

            // 最大堆存储可以选择的补给量
            PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
            int currentWater = w; // 当前水量
            int refills = 0; // 补给次数
            int prevPosition = 0; // 上一个位置

            for (int i = 0; i <= n; i++) {
                // 当前目标点,补给站或终点
                int currentPosition = (i < n) ? stations[i][0] : d;
                int distance = currentPosition - prevPosition;

                // 如果当前水量不足以到达下一个站点,进行补给
                while (currentWater < distance) {
                    if (maxHeap.isEmpty()) {
                        return -1; // 无法到达
                    }
                    currentWater += maxHeap.poll(); // 从最大堆中取最大水量
                    refills++;
                }

                currentWater -= distance;
                prevPosition = currentPosition;

                // 如果是补给站,将其水量加入堆中
                if (i < n) {
                    maxHeap.offer(stations[i][1]);
                }
            }

            return refills;
        }

        public static void main(String[] args) {
            // You can add more test cases here
            int[] testPosition = {170, 192, 196, 234, 261, 269, 291, 404, 1055, 1121, 1150, 1234, 1268, 1402, 1725, 1726, 1727, 1762, 1901, 1970};
            int[] testSupply = {443, 185, 363, 392, 409, 358, 297, 70, 189, 106, 380, 130, 126, 411, 63, 186, 36, 347, 339, 50};

            System.out.println(solution(10, 4, new int[]{1, 4, 7}, new int[]{6, 3, 5}) == 1);
            System.out.println(solution(2000, 200, testPosition, testSupply) == 5);
        }

}

外链:绿洲之旅:最少次数补给探索

;