徒步旅行中的补给问题
问题描述
小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) 收藏 举报
浙公网安备 33010602011771号