徒步旅行中的补给问题

问题描述

小R正在计划一次从地点A到地点B的徒步旅行,总路程需要 N 天。为了在旅途中保持充足的能量,小R每天必须消耗1份食物。幸运的是,小R在路途中每天都会经过一个补给站,可以先购买完食物后再消耗今天的1份食物。然而,每个补给站的食物每份的价格可能不同,并且小R在购买完食物后最多只能同时携带 K 份食物。

现在,小R希望在保证每天食物消耗的前提下,以最小的花费完成这次徒步旅行。你能帮助小R计算出最低的花费是多少吗?

输入

n 总路程需要的天数
k 小R最多能同时携带食物的份数
data[i] 第i天补给站每份食物的价格

输出

返回完成这次徒步旅行的最小花费

约束条件

1 < n,k < 1000
1 < data[i] < 10000
测试样例
样例1:

输入:n = 5 ,k = 2 ,data = [1, 2, 3, 3, 2]
输出:9

样例2:

输入:n = 6 ,k = 3 ,data = [4, 1, 5, 2, 1, 3]
输出:9

样例3:

输入:n = 4 ,k = 1 ,data = [3, 2, 4, 1]
输出:10

思路分析

考虑当前第$i$天,小R需要吃点一份食物,现在考虑这份食物是前面几天购买的。
为了保证花费最少,我们应该在$[max(0, i-k+1), i]$这个时间段内选择最便宜的购买。
核心就是维护一段长为$k$的序列内的最小值。

时间复杂度:$O(nk)$
空间复杂度:$O(1)$

int solution(int n, int k, std::vector<int> data) {
    // Edit your code here

    int minCoast = 0;
    for (int i = 0; i < n; ++i) {
        int minPrice = data[i];
        for (int j = std::max(0,i-k+1); j < i; j++) {
            minPrice = std::min(minPrice, data[j]);
        }
        minCoast += minPrice;
    }
    return minCoast;
}

优化

滑动窗口+双端队列

时间复杂度$O(n)$
空间复杂度$O(k)$
使用双端队列维护$[max(0, i-k+1), i]$的区间最小值。
双端队列中存储data的索引,队首的索引为当前要维护区间的最小值的索引。
如果队首索引超出要维护区间的范围,则在前端弹出索引。
添加索引的时候,从后开始检查,弹出大于当前索引对应的值的索引,因为如果队列中存在大于当前要添加的索引对应值的索引的话,那么它一定不会成为后续区间的最小值。

int solution(int n, int k, std::vector<int> data) {
    // Edit your code here
    int minCoast = 0;
    std::deque<int> minPrice;
    for (int i = 0; i < n; ++i) {
        if (!minPrice.empty() && minPrice.front() < i-k+1) {
            minPrice.pop_front();
        }
        while (!minPrice.empty() && data[minPrice.back()] >= data[i]) {
            minPrice.pop_back();
        }
        minPrice.push_back(i);
        minCoast += data[minPrice.front()];
    }
    return minCoast;
}

线段树

想到区间维护最小值,那么我们自然可以想到使用经典的区间维护算法--线段树 :)
时间复杂度 建树$O(n)$ 查询$O(logn)$
空间复杂度 $O(n)$

class SegmentTree {
private:
    std::vector<int> tree; // 存储线段树
    int n;            // 数组的大小
    const int MAX = 0x3f3f3f3f;
    // 构建线段树
    void buildTree(const std::vector<int>& nums, int node, int start, int end) {
        if (start == end) {
            tree[node] = nums[start];
        } else {
            int mid = (start + end) / 2;
            int leftChild = 2 * node + 1;
            int rightChild = 2 * node + 2;
            buildTree(nums, leftChild, start, mid);
            buildTree(nums, rightChild, mid + 1, end);
            tree[node] = std::min(tree[leftChild], tree[rightChild]);
        }
    }

    // 查询区间最小值
    int queryRange(int node, int start, int end, int L, int R) {
        if (R < start || L > end) {
            return MAX;
        }
        if (L <= start && end <= R) {
            return tree[node];
        }
        int mid = (start + end) / 2;
        int leftChild = 2 * node + 1;
        int rightChild = 2 * node + 2;
        int leftMin = queryRange(leftChild, start, mid, L, R);
        int rightMin = queryRange(rightChild, mid + 1, end, L, R);
        return std::min(leftMin, rightMin);
    }
public:
    SegmentTree(const std::vector<int>& nums, int _n) {
        n = _n;
        tree.resize(4 * n, MAX);
        buildTree(nums, 0, 0, n - 1);
    }
    int rangeMin(int L, int R) {
        return queryRange(0, 0, n - 1, L, R);
    }
};
int solution(int n, int k, std::vector<int> data) {
    // Edit your code here
    int minCoast = 0;
    SegmentTree segTree(data, n);
    for (int i = 0; i <= n - k; ++i) {
        minCoast += segTree.rangeMin(std::max(0, i-k+1), i);
    }
    return minCoast;
}

posted on 2025-01-20 17:24  sinker2267  阅读(72)  评论(0)    收藏  举报

导航