算法题题解记录——2. 徒步旅行中的补给问题

396 阅读2分钟

题目

www.marscode.cn/practice/r3…

image.png

关键词

动态规划贪心

方法一:动态规划

思路

旅行到第i天的最小花费不会因为后一天的食物价格而变化(无后效性),因此我们容易想到动态规划求解。那我们可以设置一个状态数组dpdp[i]表示旅行到第i天时的最小花费。

因为一份食物最多存留k天,要保证dp[i]花费最小,当天的食物需要在最近的k天中买最小价格的食物。因此得到状态转移方程:

dp[i]=dp[j]+min(data[j+1],...,data[i]),wherej[ik,i1] dp[i] = dp[j] + min(data[j+1], ..., data[i]) , where j ∈ [i-k, i-1]

代码


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)

总结

贪心和动态规划都强调将大问题分解为子问题,都强调子问题的无后效性。而他们的不同之处在于贪心总是要在面对每个子问题时做出抉择,不能回退,动态规划则需要状态数组来保存每个子问题的解,后一个解根据前面的解来求出答案。因此,往往动态规划的空间复杂度较高,但思路较容易想到,找准状态意义和转移方程则能得出答案。