题目
关键词
动态规划、贪心
方法一:动态规划
思路
旅行到第i天的最小花费不会因为后一天的食物价格而变化(无后效性),因此我们容易想到动态规划求解。那我们可以设置一个状态数组dp,dp[i]表示旅行到第i天时的最小花费。
因为一份食物最多存留k天,要保证dp[i]花费最小,当天的食物需要在最近的k天中买最小价格的食物。因此得到状态转移方程:
代码
function solution(n, k, data) {
// Edit your code here
let dp = [data[0]]
for (let i = 1; i < data.length; i++) {
dp[i] = dp[i - 1] + Math.min(...data.slice(Math.max(i - k + 1, 0), i + 1))
}
return dp[dp.length - 1];
}
function main() {
// Add your test cases here
console.log(solution(5, 2, [1, 2, 3, 3, 2]) === 9);
}
main();
复杂度分析
-
时间复杂度:
动态规划的时间复杂度计算:状态数 * 状态转移方程的时间复杂度,即
O(n * k) -
空间复杂度:
使用了一个长度为n的数组
dp来存储每一天的最小花费,因此空间复杂度为O(n)。
方法二:贪心
思路
和动态规划一样,贪心也擅长解决最优子结构的问题,子问题的最优解最终能递推到最终问题的最优解。按正常流程是当天要预见将来花费,如果当天花费最小则尽可能的买多。我这里用“后悔贪心”,即先只买当天的,到第二天再“补买”,补买的时候挑过去k天(因为食物最多存留k天)中最便宜的:
代码
function solution(n, k, data) {
// Edit your code here
let ans = 0
let mincout = 10000
let currentFood = 0
for (let i = 0; i < n; i++) {
if (currentFood < 1) {
mincout = Math.min(...data.slice(Math.max(i - k + 1, 0), i + 1))
ans += mincout
currentFood++
}
currentFood--
}
return ans
}
function main() {
// Add your test cases here
console.log(solution(5, 2, [1, 2, 3, 3, 2]) === 9);
console.log(solution(6, 3, [4, 1, 5, 2, 1, 3]) === 9);
console.log(solution(4, 1, [3, 2, 4, 1]) === 10);
}
main();
复杂度分析
-
时间复杂度:
O(n * k) -
空间复杂度:
O(1)
总结
贪心和动态规划都强调将大问题分解为子问题,都强调子问题的无后效性。而他们的不同之处在于贪心总是要在面对每个子问题时做出抉择,不能回退,动态规划则需要状态数组来保存每个子问题的解,后一个解根据前面的解来求出答案。因此,往往动态规划的空间复杂度较高,但思路较容易想到,找准状态意义和转移方程则能得出答案。