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