本质:动态规划问题的本质其实也是穷举,一般动态规划都需要我们来求最值
动态规划最本质的其实是,需要找到他的状态选择式子
动态规划步骤
- 确认
dp数组,确定其小标的含义,大小等 - 确认递推公式
- 进行
dp数组的初始化 - 遍历顺序
- 自己推导出大概的
dp数组(方便后续debug)
找零钱问题
暴力递归
方法:
- 现在给你一个
amount,让你求出最小算出amount的硬币数 - 那么我们就可以想到,只要我们求出
amount-coin的最小硬币数,那么我们将num+1,不就可以得到amount的最小嘛 - 所以要求出最小,我们肯定是使用
Math.min(),来比较是之前的那个小,还是现在的num+1小 - 因为我每一次都有很多种可能,所以需要遍历
for(let coin of coins),计算出amount-coin的需要硬币值 - 这时候我们就需要注意
base条件amount === 0,我们是return 0amount <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)之类的,所以我们需要使用一个数组
备忘录递归
定义:我们所说的对于递归的优化,其实都是建立在,我们的暴力递归是正确的前提下的
方法:
- 我们一般在做递归题目的时候都是建议画出
递归树 - 但是观察节点,我们就会发现,其实有很多节点他们是重复出现的,所以,就需要剪枝
- 这时候我们会初始化一个数组,让数组来记录我们之前计算出来的
num数量 - 当我们下次,我们就可以先判断,之前是不是已经出现过了这个
amount,知道了这个num,那么我们就可以直接返回 - 没有的话,就记录下来
- 这样就可以说清楚,我们为啥需要
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)
};
爬楼梯
方法:
- 其实我们可以这么理解,因为我们每次只可以走1或2步,所以,所以我们只需要想
- 当我现在
n阶楼梯要走时,那么我就可以完全留出1或2步 - 那么我如何走到只剩一步呢?就需要查看
dp[n-1],那么他再往上走一步,就到了dp[n] - 如何查看最后第二步呢?查看
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]
};
参考链接: