[算法整理]动态规划

126 阅读1分钟

本质:动态规划问题的本质其实也是穷举,一般动态规划都需要我们来求最值

动态规划最本质的其实是,需要找到他的状态选择式子

动态规划步骤

  1. 确认dp数组,确定其小标的含义,大小等
  2. 确认递推公式
  3. 进行dp数组的初始化
  4. 遍历顺序
  5. 自己推导出大概的dp数组(方便后续debug

找零钱问题

链接:322. 零钱兑换 - 力扣(LeetCode)

暴力递归

方法:

  1. 现在给你一个amount,让你求出最小算出amount的硬币数
  2. 那么我们就可以想到,只要我们求出amount-coin的最小硬币数,那么我们将num+1,不就可以得到amount的最小嘛
  3. 所以要求出最小,我们肯定是使用Math.min(),来比较是之前的那个小,还是现在的num+1
  4. 因为我每一次都有很多种可能,所以需要遍历for(let coin of coins),计算出amount-coin的需要硬币值
  5. 这时候我们就需要注意base条件
    • amount === 0,我们是return 0
    • amount <0,我们是return -1

代码:

function coinChange(coins: number[], amount: number): number {
    // 暴力递归
    // base条件,当总金额为0,那么我们就是只需要0
    
    const dp = (amount:number):number=>{
        if(amount === 0){
            return 0
        }
        if(amount<0){
            return -1
        }
        let res = Number.MAX_VALUE
        let count
        for(let value of coins){
            count = dp(amount-value)
            if(count === -1){
                continue
            }
            res = Math.min(res,count+1)
            
        }
        return res === Number.MAX_VALUE?-1 :res
    }
    return dp(amount)
};
console.log(coinChange([1, 2, 5],11));

但是你这样做,是不能通过的,原因:我们有太多的是重复的,比如,我们可能重复计算了f(9)之类的,所以我们需要使用一个数组

备忘录递归

定义:我们所说的对于递归的优化,其实都是建立在,我们的暴力递归是正确的前提下的

方法:

  1. 我们一般在做递归题目的时候都是建议画出递归树
  2. 但是观察节点,我们就会发现,其实有很多节点他们是重复出现的,所以,就需要剪枝
  3. 这时候我们会初始化一个数组,让数组来记录我们之前计算出来的num数量
  4. 当我们下次,我们就可以先判断,之前是不是已经出现过了这个amount,知道了这个num,那么我们就可以直接返回
  5. 没有的话,就记录下来
  6. 这样就可以说清楚,我们为啥需要amount+1长度了

代码:

function coinChange(coins: number[], amount: number): number {
    // base条件,当总金额为0,那么我们就是只需要0
    // 同样的我们采用字典
    // 首先设置一个数组
    let arr = new Array<number>(amount + 1).fill(-2)
    // 因为-1作为了一个返回结果
    const dp = (amount:number,arr:number[]):number=>{
        if(amount === 0){
            return 0
        }
        if(amount<0){
            return -1
        }
        // 如果我已经知道啦arr,那么我就可以直接使用
        if (arr[amount] !== -2) {
            return arr[amount]
        }
        let res = Number.MAX_VALUE
        let count
        for(let value of coins){
            count = dp(amount-value,arr)
            if(count === -1){
                continue
            }
            res = Math.min(res,count+1)
            
        }
        arr[amount] = res === Number.MAX_VALUE?-1 :res
        return arr[amount]
    }
    return dp(amount,arr)
};

爬楼梯

链接:70. 爬楼梯 - 力扣(LeetCode)

方法:

  1. 其实我们可以这么理解,因为我们每次只可以走1或2步,所以,所以我们只需要想
  2. 当我现在n阶楼梯要走时,那么我就可以完全留出12
  3. 那么我如何走到只剩一步呢?就需要查看dp[n-1],那么他再往上走一步,就到了dp[n]
  4. 如何查看最后第二步呢?查看dp[n-2],那么他再往上走2步,就到了dp[n]

代码

function climbStairs(n: number): number {
// 确认dp数组
// 长度为n的dp数组,dp[i]里面存放的是,爬楼的方法
let dp = new Array<number>(n+1).fill(-1)
// base条件
dp[0] = 1
dp[1] = 1
for(let i  = 2;i<=n;i++){
    dp[i] = dp[i-1]+dp[i-2]
}
return dp[n]
};

参考链接:

动态规划解题套路框架 :: labuladong的算法小抄 (gitee.io)

代码随想录 (programmercarl.com)