动态规划
动态规划一般用数组记录下每一步的最优值,返回最后一步的值就是最优结果,相对于递归解法消除了重复计算的部分,提高的效率。
一、动态规划解决的问题特点
- 计数:有多少种方式从A到B、多少种方式取得某值、、、
- 求最优解:最大值最小值、路径上最大数据和。
- 存在性问题:能不能选出和为k的数、、、
选定算法前一定要判断是否为动态规划解题、有时候问题相似但不能用动态规划求解。
二、动态规划的步骤
以LeetCode 322 零钱兑换 为例:
给定不同面额的硬币 coins 和一个总金额 amount。
编写一个函数来计算可以凑成总金额所需的最少的硬币个数。
如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
1.确定状态
首先要确定状态就是解决问题的最后一步和确定子问题。
-
最后一步
就是找到解决最优策略的最后一步;
例题中最后一步就是最少用k枚硬币达到amount数额
-
子问题(与原问题一样,规模变小)
知道了最后一步后向前推,最少用k-1枚硬币达到amount-coink
简化定义就是F(x)=拼出amount的最少硬币数
如果coink是2,F(11)=F(11-2)+1枚
如果coink是5,F(11)=F(11-5)+1枚
对于示例1:F(11)=min{ F(11-1)+1 , F(11-2)+1 , F(11-5)+1 }
2.作出转移方程
由之前的结果写出转移方程
对于示例1:F(x)=min{ F(x-1)+1 , F(x-2)+1 , F(x-5)+1 }
3.注意初始条件和边界条件
对于示例1:
- x的值不能小于0
- 不能拼凑的amount值应该怎么处理(大多数时候定义正无穷方便,有时候定义-1这种可能更方便)虽然这里1和2可以拼凑所有正整数
- 初始条件F(0)=0,转移方程算不出来,但是又需要用的值
4.确定计算顺序
动态规划一般都是以自底向上进行循环迭代计算,
而带备忘录的递归则是自顶向下进行计算。
在例题中:转移方程计算左边时,右边的内容都需要已经有结果了
计算F(11)时需要F(10)、F(9)、F(6)的具体值,那么从小到大计算(根据题目以及选定解题方式选定计算顺序)。
最后是编码,与理论不一样,程序编码中很多要注意的地方,如
public int coinChange(int[] coins, int amount) {
// 创建数组
//1. 注意从0-amount有amount+1个数
int[] f = new int[amount + 1];
// 2.注意初始化
f[0] = 0;
for (int i = 1; i < f.length; i++) {
f[i] = Integer.MAX_VALUE;
for (int j = 0; j < coins.length; j++) {
//3.注意F(x)中x>0,并且Max+1会越界
if (i - coins[j] >= 0 && f[i - coins[j]] != Integer.MAX_VALUE) {
f[i] = Math.min(f[i - coins[j]] + 1, f[i]);
}
}
}
if (f[amount] == Integer.MAX_VALUE) {
f[amount] = -1;
}
return f[amount];
}
例题
力扣 62.题不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?

例如,上图是一个7 x 3 的网格。有多少可能的路径?
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
示例 2:
输入: m = 7, n = 3
输出: 28
提示:
1 <= m, n <= 100
题目数据保证答案小于等于 2 * 10 ^ 9
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/unique-paths
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题步骤:
- 确定最后一步:到达右下角的路径数。
- 确定子问题:到达当前格子路径数量=到达上面各自数量+到达左边格子数量。
- 做出转移方程:f(x)(y)=f(x-1)(y)+f(x)(y-1)
- 确定初始条件和边界:第一行和第一列每格到达路径数都为1。
- 确定计算顺序:计算当前格需要知道上面格子与左边格子的路径数量->从前往后。
编码:
public int uniquePaths(int m, int n) {
// 创建数组
int[][] f = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 || j == 0) {
f[i][j] = 1;//初始化第一行和第一列
} else {
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
}
return f[m - 1][n - 1];
}

例题 63.不同路径Ⅱ
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。
说明:m 和 n 的值均不超过 100。
示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/unique-paths-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题步骤:
与上一题一样,只是初始条件和边界变化:
- 确定最后一步:到达右下角的路径数。
- 确定子问题:到达当前格子路径数量=到达上面各自数量+到达左边格子数量(前提为计算的格子都不包含障碍物)。
- 做出转移方程:f(x)(y)(不是障碍物)=f(x-1)(y)(不是障碍物)+f(x)(y-1)(不是障碍物)
- 确定初始条件和边界:有障碍物就为0;第一格不是障碍物就为1。
- 确定计算顺序:计算当前格需要知道上面格子与左边格子的路径数量->从前往后。
编码:
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] f = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (obstacleGrid[i][j] == 1) {
f[i][j] = 0;//是障碍物就为0
} else if (i == 0 && j == 0) {//第一格不是障碍物为1
f[i][j] = obstacleGrid[i][j] == 1 ? 0 : 1;
} else {//没初始化第一行第一列,判断一下防止越界
f[i][j] = (i - 1 >= 0 ? f[i - 1][j] : 0) + (j - 1 >= 0 ? f[i][j - 1] : 0);
}
}
}
return f[m - 1][n - 1];
}

例题 70.爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/climbing-stairs
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题步骤:
- 确定最后一步:到达楼顶
- 确定子问题:到达当前阶层方法数量=前一层数量踏一步+上上层数量踏两步。
- 做出转移方程:f(x)=f(x-1)+f(x-2)
- 确定初始条件和边界:第一层为1,第二层为2。
- 确定计算顺序:计算当前层需要知道上一层与上上层数量->从前往后。
编码:
public int climbStairs(int n) {
int[] f = new int[n];
f[0] = 1;
if (n > 1) {
f[1] = 2;
for (int i = 2; i < f.length; i++) {
f[i] = f[i - 1] + f[i - 2];
}
}
return f[n - 1];
}

不用数组的话:
public int climbStairs(int n) {
int penult=1,last=1,current=1;//倒数第二、倒数第一、当前层
for (int i = 2; i < n+1; i++) {
current=penult+last;
penult=last;
last=current;
}
return current;
}

本文介绍了动态规划的基本概念,强调了其在解决计数、求最优解和存在性问题上的应用。通过LeetCode的三个示例详细阐述了动态规划的四个步骤:确定状态、建立转移方程、设定初始条件和边界条件,以及确定计算顺序。并提供了不同问题的编码实现思路。

被折叠的 条评论
为什么被折叠?



