这是一道经典的贪心算法题目,要求规划从起点到终点的购买策略,以使总花费最小。下面整理题目理解、解题思路和实现步骤,方便复习和参考。
问题分析
小U需要在从地点A到地点B的徒步旅行中保证每天有食物,同时需要从不同价格的补给站中购买以实现 总花费最小。关键问题在于如何规划每一天的购买数量和购买地点。
输入输出分析
-
输入:
-
M:总路程的天数。 -
N:补给站的数量。 -
p:补给站信息,p[i] = [A, B]表示第A天有补给站,每份食物的价格为B。- 保证第
0天一定有补给站。 - 补给站按照天数从小到大排列。
- 保证第
-
-
输出:
- 购买食物的最小总花费。
示例
-
输入:
plaintext 复制代码 m = 5, n = 4, p = [[0, 2], [1, 3], [2, 1], [3, 2]]- 共需 5 天。
- 有 4 个补给站分别在第 0 天、第 1 天、第 2 天、第 3 天,价格依次为 2, 3, 1, 2。
-
输出:
plaintext 复制代码 7-
最优策略:
-
在第 0 天购买 2 份(消耗当天和第 1 天)。
-
在第 2 天购买 3 份(供第 2, 3, 4 天)。
- 总花费:
2×2 + 1×3 = 7。
-
-
解题思路
问题建模
-
每一天消耗一份食物。
-
每个补给站的价格不同,我们需要决定在哪些天购买多少食物,以满足全程需求,并使总花费最小。
-
贪心策略适合解决这类问题,即:
- 优先在价格便宜的补给站购买更多食物。
- 在高价补给站尽量少买,甚至不买。
核心思想
- 模拟每天的行程,同时维护当前持有的食物数量。
- 当即将缺少食物时,根据价格和剩余天数选择最优的购买策略。
- 使用 优先队列(小根堆) 来动态管理价格最低的补给站。
详细实现步骤
1. 初始化
- 使用一个变量
current_food表示当前持有的食物数量。 - 用一个优先队列(最小堆)记录当前所有可用的补给站(根据价格排序)。
- 总花费初始化为
0。
2. 按天模拟
-
遍历每一天,从第 0 天到第
M-1天。- 每天先消耗一份食物,
current_food -= 1。 - 如果当前天有补给站,将其加入优先队列(按价格排序)。
- 当食物不足时,从优先队列中购买最便宜的食物,直到满足需求。
- 每天先消耗一份食物,
3. 贪心购买
-
当天食物不足时:
- 从价格最低的补给站(堆顶)购买食物。
- 更新总花费和当前食物数量。
4. 特殊情况
- 如果在任何时刻,优先队列为空且食物不足,则问题无解。
代码实现
import java.util.PriorityQueue;
public class Main {
public static int solution(int m, int n, int[][] p) {
// Edit your code here
int[] dp = new int[m + 1];
for (int i = 1; i <= m; i++) {
dp[i] = Integer.MAX_VALUE; // Set a high initial cost for each day
}
dp[0] = 0; // Starting point has no cost
// Priority queue to store available stations, sorted by price (min-heap)
PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> a[1] - b[1]);
int stationIndex = 0;
for (int day = 0; day <= m; day++) {
// Add new stations that are available on this day
while (stationIndex < n && p[stationIndex][0] == day) {
minHeap.offer(p[stationIndex]);
stationIndex++;
}
// If we need food today and there's an available station
if (dp[day] != Integer.MAX_VALUE && !minHeap.isEmpty()) {
// Buy food at the lowest price available from the heap
int[] cheapestStation = minHeap.peek();
int price = cheapestStation[1];
// Only update dp[day + 1] if day + 1 is within bounds
if (day + 1 <= m) {
dp[day + 1] = Math.min(dp[day + 1], dp[day] + price);
}
}
}
return dp[m];
}
public static void main(String[] args) {
// Add your test cases here
System.out.println(solution(5, 4, new int[][]{{0, 2}, {1, 3}, {2, 1}, {3, 2}}) == 7);
}
}
复杂度分析
-
时间复杂度:
- 遍历所有天数和补给站:
O(M + N)。 - 堆操作(插入/弹出):
O(log N)。 - 总复杂度:
O((M + N) log N)。
- 遍历所有天数和补给站:
-
空间复杂度:
- 存储补给站的堆:
O(N)。
- 存储补给站的堆:
总结与扩展
总结
- 贪心思想:优先从价格最低的补给站购买足够的食物。
- 动态维护最优解:利用优先队列快速获取当前最便宜的补给站。
- 边界处理:确保在缺少食物时优先购买,避免因缺少食物导致旅行失败。
扩展
- 更多限制条件:如果每个补给站能购买的食物数量有限,需要在贪心时额外处理购买数量。
- 其他算法:对于补给站数量庞大的场景,可用动态规划方法优化搜索路径。