问题背景
假设一位旅行者需要穿越一片沙漠,起点到终点的距离为 D 公里,旅行者初始携带了 W 升水,每前进一公里需要消耗一升水。在穿越过程中,沿途会经过 N 个补给站,补给站的位置和可提供的水量分别由数组 position 和 supply 描述。
旅行者的目标是用最少的补给次数到达终点。如果在某个时刻发现无法前行,则无法完成任务,输出 -1。
在这一问题中,我们需要兼顾两个核心点:
- 每次补给的时机:选择适当的时机进行补给,以保证旅程能够继续。
- 补给的优先级:从多个补给选项中,选择能让旅程覆盖最远距离的补给量。
解决思路
从问题描述来看,它具备明显的贪心特性:我们需要在每个关键点(当前位置无水前行时)做出最优决策,尽量选择当前能提供最多水量的补给站,以减少未来补给次数。具体的解决思路如下:
- 按距离排序补给站 补给站的位置不一定是按顺序给出的,因此需要先将它们按距离从小到大排序。这样可以保证旅行者在沿途遇到补给站的顺序与实际前进顺序一致。
- 维护一个最大堆记录可选补给量 在每经过一个补给站时,将其可提供的水量存入最大堆中。当发现当前携带的水量不足以到达下一个目的地时,从堆中取出最大值进行补给。这种策略确保了当前的补给选择是对后续行程最有利的。
- 处理终点作为特殊情况 在所有补给站处理完后,还需要考虑终点的情况。如果当前水量仍不足以到达终点,则需要继续从最大堆中补给,否则任务失败。
- 计算补给次数 每次从堆中取水,即为一次补给。通过计数器记录补给次数,最终返回结果。
贪心算法的优势
这一问题选择贪心算法有以下几个优势:
- 局部最优解的有效性 在每次面临“是否需要补给”的选择时,选用当前能提供最大补给量的站点,总能为未来节省更多水量需求,减少补给次数。
- 时间效率高 贪心算法主要依赖排序和堆操作,时间复杂度为 O(nlogn),非常适合处理大规模输入。
- 简单易实现 贪心算法直接针对问题的局部最优策略构建解决方案,逻辑清晰,代码量少。
实现步骤详解
具体的实现可以分为以下几个步骤:
-
输入数据的预处理 将补给站信息存储为一个二维数组,并按照距离进行排序。
-
使用最大堆记录补给量 遍历每个补给站时,将其补给量加入最大堆。在水量不足时,从堆中提取最大补给量。
-
终点检查 当所有补给站处理完后,仍需检查剩余水量是否能覆盖到终点。如果无法到达,则返回 -1。
-
补给次数统计 每次补充水量,计数器加一,最后输出最少补给次数。
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]; } java.util.Arrays.sort(stations, java.util.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); } }