问题描述
小R正在计划一次从地点A到地点B的徒步旅行,总路程需要 N
天。为了在旅途中保持充足的能量,小R每天必须消耗1份食物。幸运的是,小R在路途中每天都会经过一个补给站,可以购买食物进行补充。然而,每个补给站的食物每份的价格可能不同,并且小R最多只能同时携带 K
份食物。
现在,小R希望在保证每天都有食物的前提下,以最小的花费完成这次徒步旅行。你能帮助小R计算出最低的花费是多少吗?
测试样例
样例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
解题思路:
这个问题可以通过动态规划或贪心算法来解决。在这里,我们可以使用贪心算法,因为小R每天都需要消耗食物,我们可以假设小R总是在价格最低的补给站购买尽可能多的食物,直到他的背包满了或者不再需要购买食物为止。也就是说是要找到一个滑动窗口内元素的最小值的总和。滑动窗口的大小为k
,并且窗口在数组data
上从左向右滑动。
-
初始化:创建一个
LinkedList
作为栈来存储滑动窗口内的元素,并初始化结果变量res
为0。 -
遍历数组:对于数组
data
中的每个元素,执行以下步骤:- 如果栈为空或者当前元素大于等于栈顶元素,将当前元素添加到栈顶。
- 如果当前元素小于栈顶元素,则弹出栈顶元素,直到栈为空或者当前元素大于等于新的栈顶元素,然后将当前元素添加到栈顶。
-
维护滑动窗口:当索引
i
大于等于k-1
时,意味着窗口已经填满,需要开始滑动。检查栈的第一个元素是否等于窗口最左边的元素(即data[i-k]
),如果是,则从栈中移除该元素。 -
累加结果:每次循环结束时,将栈顶元素(即当前窗口的最小值)加到结果
res
上。 -
返回结果:遍历结束后,返回累加的结果
res
。
相关知识点说明:
- 栈(Stack) :栈是一种后进先出(LIFO)的数据结构。在Java中,
LinkedList
可以当作栈使用,其提供了addLast()
和removeLast()
方法分别对应栈的push
和pop
操作。 - 滑动窗口:这是一种常用的数组/列表处理技巧,通常用于处理固定大小窗口内的元素。窗口在数组上滑动,以包含连续的子数组。
- 最小值维护:使用栈来维护滑动窗口内的最小值。栈底到栈顶的元素是单调递增的,这样栈顶始终是当前窗口的最小值。
代码实现
以下是代码实现
import java.util.*;
public class Main {
public static int solution(int n, int k, int[] data) {
// Edit your code here
int res = 0;
LinkedList<Integer> stack = new LinkedList<>();
int len = data.length;
for(int i = 0; i<len; i++){
if(stack.isEmpty() || data[i] >= stack.getLast()){
stack.addLast(data[i]);
}else{
while(!stack.isEmpty() && data[i] < stack.getLast()){
stack.removeLast();
}
stack.addLast(data[i]);
}
if(i>k-1 && stack.getFirst() == data[i-k]){
stack.removeFirst();
}
res+=stack.getFirst();
}
return res;
}
public static void main(String[] args) {
// Add your test cases here
System.out.println(solution(5, 2, new int[]{1, 2, 3, 3, 2}) == 9);
System.out.println(solution(6, 3, new int[]{4, 1, 5, 2, 1, 3}) == 9);
System.out.println(solution(4, 1, new int[]{3, 2, 4, 1}) == 10);
}
}
这个贪心算法假设了最优解是在每个补给站购买尽可能多的食物,直到背包满或者不需要再购买。这种方法在大多数情况下都是有效的,因为它总是试图在价格最低的时候购买尽可能多的食物。然而,需要注意的是,贪心算法并不总是能得到全局最优解,但在这种情况下,它通常能给出正确的答案。
时间复杂度
在solution
方法中,主要的操作是遍历数组data
,并在遍历过程中对栈stack
进行操作。以下是详细的分析:
-
遍历数组:代码包含一个循环,该循环遍历数组
data
的每个元素一次。因此,这部分的时间复杂度是O(n)。 -
栈操作:
- 在每次迭代中,可能需要将元素添加到栈中,这是O(1)操作。
- 如果当前元素小于栈顶元素,那么可能需要弹出多个元素直到找到小于或等于当前元素的元素,最坏情况下可能需要弹出栈中所有元素。然而,由于每个元素最多只会被弹出一次,因此整个算法中的总弹出次数是O(n),即使是在最坏情况下。
由于上述操作是嵌套的,但每个元素最多只会被添加和弹出一次,因此整体的时间复杂度仍然是O(n)。
空间复杂度
在solution
方法中:
- 栈
stack
:在最坏的情况下,如果数组data
中的元素是单调递增的,那么栈可能需要存储所有元素。因此,栈的空间复杂度是O(n)。 - 其他变量:如
res
、len
、i
等变量只占用常数空间,因此它们的空间复杂度是O(1)。
综合上述分析,空间复杂度是O(n)。
总结
- 时间复杂度:O(n)
- 空间复杂度:O(n)
这里假设n
是数组data
的长度。需要注意的是,尽管代码在每次迭代中都累加了栈顶元素到res
,但这并不改变时间复杂度,因为累加操作是常数时间的。