这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」
啥是动态规划?
“动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。---来自百度百科
啥题目要用动态规划去求解?
我们一般是用动态规划来解决最优问题。比如最大序列合,最长/短路径,最佳时机等等,求最优问题我们就可以考虑是否用动态规划来解决。
如何用动态规划解题?
- 确定递推状态
- 确定状态转移方程
- 确定边界条件
- 实现算法
举个栗子?
力扣第746题 使用最小花费爬楼梯
从题目最小花费
我们就可以看出,这题可以用动态规划去解。我们先画个图,然后来一步步按上述步骤解题:
- 确定递推状态 f(x) = y;
y是我们求的值,即走到第i个台阶需要花费的体力。
那x是引起y变化的因素,因素有哪些?这道题只有台阶数是因变量,所以我们定义x为台阶数。
- 确定状态转移方程
有题目和图示,我们可以看出,如何走到dp[i]?要么从倒数第一个台阶上去,花费为dp[i-1] + costs[i-1]
,要么从倒数第二个台阶上去,花费为dp[i-2]+costs[i-2]
。而题目要求是最小花费,所以取两者的较小值就行。
所以我们可以定义状态转移方程为:
dp[i] = min(dp[i-1] + costs[i-1], dp[i-2] + costs[i-2])
- 定义边界条件
我们根据状态转移方程可以知道,想知道第i个台阶的花费,就得知道上一个和上上一个台阶的花费,那边界条件就是第一阶和第二阶。而爬上第一阶和第二阶都不需要花费。(第一阶和第二阶都可以一次性跨上去,这里就不需要花费。我们只有从需要花费的台阶往上爬才需要消费costs[i],例如从第一阶或者第二阶往上爬)
dp[0] = 0;
dp[1] = 0;
- 实现算法
var minCostClimbingStairs = function(cost) {
const n = cost.length;
const dp = []
dp[0] = 0;
dp[1] = 0;
for (let i = 2; i <= n; i++) {
dp[i] = Math.min((dp[i - 1] + cost[i - 1]), (dp[i - 2] + cost[i - 2]))
}
return dp[n]
};
OK,下面来几道简单的动态规划题目
杨辉三角
这道题,f(x) = y。我们直接定义y是第i行的数据好像不太合适,这样y是个数组,我们想他是个准确的值。那我们就定义y是第i行第j位的数据,这样题目的求值就是第i行的所有数据了。由此可得状态转移方程为
// dp[i][j]表示由上往下求第i行第j位的值
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
代码如下:
var getRow = function(rowIndex) {
// 数组长度
const n = rowIndex + 1;
// 初始化二维数组,并初始化值为1,这样杨辉三角的边界条件就省略了
const dp = new Array(n).fill(1).map(() => new Array(n).fill(1))
for(let i = 1; i < n; i++) {
for (let j = 1; j < i; j++) {
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
}
}
return dp[n - 1]
};
打家劫舍
题目看着就刺激。。。学好算法再就业也可以利润最大化。。。
我们要求的是偷窃第i家房屋所得的最大金钱数,因为题目告诉我们不能连着偷,所以偷到第i家的最大金钱数是偷到第i-1家的最大金钱数
和偷到第i-2家的最大金钱数再加上第i家的金钱之和
两者中取较大值。
所以我们直接定义状态转移方程dp[i] = max(dp[i-1], dp[i-2] + nums[i])
由状态转移方程可知,dp[i]只与dp[i-1]和dp[i-2]有关,所以我们可以只定义一个长度为3的数组,然后轮番替换值,节省空间。这种优化方法叫做滚动数组
。
代码如下:
var rob = function(nums) {
const n = nums.length;
if (n === 1) return nums[0]
const dp = new Array(3)
dp[0] = nums[0]
dp[1] = Math.max(nums[0], nums[1])
for (let i = 2; i < n; i++) {
const idx = i % 2;
const idx_pre = (i - 1) % 2;
const idx_pre_pre = (i - 2) % 2
dp[idx] = Math.max(dp[idx_pre], dp[idx_pre_pre]+nums[i])
}
return dp[(n - 1) % 2]
};
粉刷房子
刚™偷完房子就粉刷房子。。可能是出来劳改来了。。。
这道题就留给大家自己思考啦。随便列个状态转移方程留个言?上面的题目还有优化的空间,也希望大家可以留言讨论~等我再学两天,再出个动态规划2.0,3.0难度的题目解析。
😀😀😀欢迎大家讨论,看完记得点个👍🏻哦。本文的代码已放Github,还有其他动态规划的题目,没有写进文章,但是git里都有写。里面还有之前算法的代码,后续代码也会陆续更新,欢迎大家不吝赐教。