最近在刷leetcode动态规划的部分,感觉这部分挺有意思的,想起来好久没有写博客,因此来分享一下自己在写动态规划题目时的一点点感悟。
动态规划是什么
动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。 它将复杂的问题简单化,能减少不必要的计算,从而提升运算的效率,常用于解决最优子结构和重叠子结构的问题。
以上的回答来自百度百科,可能有点抽象,但是简单来说,动态规划解决问题的关键是依靠将问题拆分成子问题,不断优化子结构,从而得到答案。
下面,我们从一道最简单的动态规划问题出发,了解一下动态规划的解决过程。
动态规划基础
求出第n个斐波那契数列。 斐波那契数列: n = (n-1) + (n-2)
在看到这道题的时候,可能会下意识用递归来做
function fib(n){
if(n==1||n==2){
return 1
}
return fib(n-1)+fib(n-2)
}
但是这样子是会超时的,为什么呢? 我们将解题过程具体化:
我们会发现3的部分被计算了两次,如果n越多,重复计算的就越多,如果能节约这些计算的过程就好了。
因此,我们可以定义dp数组为dp[i]是第i个斐波那契数,存储之前求出的解,这样子就能有效的避免重复运算,这就是动态规划的核心,从最优子结构出发一步步优化,最后得出答案。
// 分解子问题 第n个数等于第n-1的数 加上第n-2的数
function fib(n){
const dp = [0,1] // dp[i]表示第i个斐波那契数
for(let i = 2;i < n;i++){
//存储子问题的解
d[i] = d[i-1]+d[i-2]
}
//推出大问题的解
return d[n]
}
}
我们通过dp数组来存储解( 其实这里用两个变量来就能解决问题,空间复杂度为O(1) ),将求第n个数的斐波那契数转化为dp[i] = dp[i-1] + dp[i-2]。
动态规划的思路过程
什么样的问题适合使用动态规划,或者我们如何判断这道题要用到动态规划?
当我们把问题的解罗列出来后 发现有重叠的子问题,那么就可以考虑动态规划
动态规划其实是有一种套路的思路:
- 判断:是否有重叠子问题 + 最优子结构?(先想暴力解,看是否有重复计算)
- 定义:DP 数组的下标和值分别代表什么?(紧扣问题阶段和目标)
- 初始化:最小子问题的解是什么?(边界值如何设定)
- 递推:当前状态的所有来源是什么?如何从来源推导当前解?(结合目标写公式)
- 求解:最终目标对应 DP 数组的哪个值?(可选:是否能优化空间?)
动态规划例题分析
Leetcode上有许多关于动态规划的问题,我们通过几道例题来理解一下:
零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。每种硬币的数量是无限的。
var coinChange = function(coins=[1,2,5],amount=11){
//用定义好的硬币面额 拼凑出最少的硬币数
//我们定义dp数组为i金额所需要最少硬币数
const dp = new Array(amount+1).fill(amount+1)
dp[0] = 0
//对于dp[i] = dp[i-C] + 1 C为钱币的面额
for(let i = 1;i <= amount;i++){
//尝试每种硬币
for(let j = 0; j <= coins.length;j++){
if(coins[j] <= i){
dp[i] = Math.min(dp[i-coins[j]]+1,dp[i])
}
}
}
return dp[amount]>amount?-1:dp[amount]
}
0-1背包问题
给你一个可以装载重量为W的背包和N个物品 每个物品都有重量和价值俩个属性 其中第i个物品的重量为wt[i],价值为val[i], 现在让你用这个背包装物品,每个物品只能用一次,请问最多能装载的价值是多少?
var bag = (W=4,wt=[2, 1, 3],val=[4, 2, 3])=>{
//定义N为物品数量
const N = wt.length
//考虑到题目中有重量表和价值表
//我们定义dp为:对于前i个物品 背包容量为j时最多的价值 dp[N][W]就是问题的解
const dp = new Array(N+1).fill(),map(()=>new Array(W+1).fill(0))
// i代表物品数量 j代表价值
for(let i = 1;i <= N;i++){
for(let j = 1;j <= W;j++){
//我们要考虑一下放不放背包
//如果背包容量
if(j-wt[i-1] < 0){
dp[i][j] = dp[i-1][j]
}else{
dp[i][j] = Math.max(dp[i-1][j-wt[i-1]]+val[i-1],dp[i-1][j])
}
}
}
return dp[N][W]
}
结语
动态规划问题帮我完善了解决问题的思维方式,让我学会了如何将复杂问题分解为简单的子问题,并通过存储中间结果来优化计算。虽然初学时会觉得有些抽象,但一旦掌握了核心思想,就会发现它是一种非常强大且优雅的算法设计技术。
希望这篇分享对正在学习动态规划的你有帮助!继续刷题,一起进步!