青训营X豆包MarsCode 技术训练营 | 豆包MarsCode AI 刷题 |补给站最优花费问题

51 阅读4分钟

要解决小明徒步问题,目标是在确保每天消耗1份食物的情况下,以最小的花费完成M天的徒步。为了实现这一目标,我们需要制定一个策略,选择在合适的补给站购买足够的食物,以覆盖接下来的几天,并尽可能利用价格较低的补给站。

解题思路

  1. 添加虚拟补给站: 为了简化问题,我们在终点B(第M天)添加一个虚拟的补给站,价格为0元。这样可以确保我们最终能覆盖到终点。
  2. 寻找下一个价格更低的补给站: 对于每一个补给站,找到其后面第一个价格更低的补给站。这可以通过使用单调栈(Monotonic Stack)高效地实现。
  3. 计算最小花费: 从起点开始,依次在每个补给站购买足够的食物,以达到下一个价格更低的补给站。这样可以确保在价格较高的补给站购买的食物尽可能少,从而最小化总花费。

详细步骤

  1. 初始化

    • 读取M(总天数)和N(补给站数量)。
    • 读取N个补给站的信息,并将其存储在一个数组中。
    • 在数组末尾添加一个虚拟补给站,位置为M天,价格为0元。
  2. 使用单调栈找到每个补给站的下一个价格更低的补给站

    • 从后往前遍历补给站。
    • 使用一个栈来保持补给站的索引,栈内的补给站价格递增。
    • 对于当前补给站,弹出所有价格不低于当前补给站的补给站,直到找到第一个价格更低的补给站。
    • 记录这个补给站的位置作为当前补给站的下一个价格更低的补给站。
  3. 遍历补给站,计算最小花费

    • 从起点开始,依次在每个补给站购买足够的食物,以覆盖到下一个价格更低的补给站。
    • 累加每次购买的花费,直到覆盖整个M天的徒步。

代码实现

以下是基于上述思路的Java实现:

import java.util.ArrayDeque;

public class Main {
    public static int solution(int m, int n, int[][] p) {
        // 创建一个新的数组,包含N+1个补给站,最后一个是虚拟补给站
        int[][] stations = new int[n + 1][2];
        for(int i = 0; i < n; i++) {
            stations[i][0] = p[i][0]; // A天
            stations[i][1] = p[i][1]; // B价格
        }
        stations[n][0] = m; // 虚拟补给站的位置
        stations[n][1] = 0; // 虚拟补给站的价格

        // 计算每个补给站的下一个价格更低的补给站
        int[] nextLower = new int[n + 1];
        ArrayDeque<Integer> stack = new ArrayDeque<>();
        for(int i = n; i >= 0; i--){
            while(!stack.isEmpty() && stations[stack.peekLast()][1] >= stations[i][1]){
                stack.removeLast();
            }
            if(stack.isEmpty()){
                nextLower[i] = n; // 如果没有更低价格的补给站,指向虚拟补给站
            }
            else{
                nextLower[i] = stack.peekLast();
            }
            stack.addLast(i);
        }

        // 遍历补给站,计算总花费
        long total = 0;
        int current = 0;
        while(current < n){
            int j = nextLower[current];
            int distance = stations[j][0] - stations[current][0];
            total += (long) distance * stations[current][1];
            current = j;
        }

        return (int) total;
    }

    public static void main(String[] args) {
        // 测试用例
        System.out.println(solution(5, 4, new int[][]{
            {0, 2}, 
            {1, 3}, 
            {2, 1}, 
            {3, 2}
        }) == 7); // 输出:7

        // 其他测试用例
        System.out.println(solution(2, 1, new int[][]{
            {0, 1}
        }) == 2); // 输出:2

        System.out.println(solution(3, 2, new int[][]{
            {0, 1},
            {1, 0}
        }) == 1); // 输出:1

        System.out.println(solution(5, 1, new int[][]{
            {0, 0}
        }) == 0); // 输出:0
    }
}

代码解析

  1. 补给站数组的构建

    • stations数组包含所有N个补给站的信息,以及一个虚拟补给站。
    • 虚拟补给站的位置为第M天,价格为0元。
  2. 单调栈的使用

    • 我们从后往前遍历所有补给站。
    • 对于每个补给站,弹出栈中所有价格不低于当前补给站价格的补给站。
    • 栈顶元素即为当前补给站的下一个价格更低的补给站。
    • 如果栈为空,说明当前补给站之后没有价格更低的补给站,指向虚拟补给站。
  3. 总花费的计算

    • 从起点开始,依次在每个当前补给站购买足够的食物,以覆盖到下一个价格更低的补给站。
    • 累加每次购买的花费,直到覆盖所有M天。
  4. 测试用例

    • 提供了多个测试用例,包括样例输入和其他边界情况,确保算法的正确性。

复杂度分析

  • 时间复杂度:O(N),其中N是补给站的数量。单调栈的每个元素最多进出栈一次。
  • 空间复杂度:O(N),用于存储补给站信息和单调栈。

总结

通过使用单调栈,我们能够高效地找到每个补给站的下一个价格更低的补给站,并据此制定购买策略,以最小化总花费。这种方法不仅简洁,而且在处理大规模数据时也能保持高效。