动态规划
动态规划的定义
动态规划解题思路和顺序
- 确定dp数组以及dp[i]所代表的含义
- 确定状态转移方程
- 确定如何初始化
- 确定dp数组遍历顺序以及遍历深度
- 在遍历的过程中使用实际数值推导状态转移方程
相关例题
简单动态规划
- 斐波那契数列
斐波那契数,通常⽤ F(n) 表⽰,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后
⾯的每⼀项数字都是前⾯两项数字的和。也就是: F(0) = 0,F(1) = 1 ,F(n) = F(n - 1) + F(n - 2),其中 n> 1 给你n ,请计算 F(n) 。
⽰例 1:
输⼊:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
⽰例 2:
输⼊:3
输出:2
public int fibNum(int n) {
//1、确定dp数组以及下标的含义(确定dp数组所代表的含义、长度)
int[] dp = new int[n + 1];
//2、确定转移方程
// dp[n] = dp[n-1]+dp[n-2]
//3、初始化数组
dp[0] = 0;
dp[1] = 1;
//4、遍历数组(确定遍历顺序、遍历长度)
for (int i = 2; i <= n; i++) {
//5、举例推导数组
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
- 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的⽅法可以爬到楼顶呢? 注意:给定 n 是⼀个正整数。
⽰例 1:
输⼊: 2
输出: 2
解释: 有两种⽅法可以爬到楼顶。
1 阶 + 1 阶
2 阶
⽰例 2:
输⼊: 3
输出: 3
解释: 有三种⽅法可以爬到楼顶。
1 阶 + 1 阶 + 1 阶
1 阶 + 2 阶
2 阶 + 1 阶
public static int climbStairs(int n) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
if (n == 2) {
return 2;
}
//1、确定dp数组以及下标的含义
int[] dp = new int[n + 1];
//2、确定状态转移方程
//第n阶可能是一步上来也可能是由两步上来所以,其方法是这两种的和
// dp[n] = dp[n-1] +dp[n-2];
//3、初始化dp
dp[0] = 0;
dp[1] = 1;
dp[2] = 2;
//4、遍历dp(遍历dp的时候要从初始化dp的后一位开始遍历)
for (int i = 3; i <= n; i++) {
//5、推导dp
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
我的思路:
- 题目要求到达n阶的楼顶有多少种方法,所以dp[n]的含义就是到达n阶的楼顶一共多少方法;
- 到达n阶可能是从n-1到达也可能是从n-2到达,而到达n-1或n-2也有同样的可能,所以就出现了重叠子问题,这里便可得知状态转移方程为dp[i] = dp[i-1] + dp[i-2];
- 已知dp[0] = 0,dp[1] = 1,dp[2] = 2;
- 初始化之后,开始遍历,题目要求dp[n]的值所以遍历的深度为小于等于n;
- 举例代入状态转移方程即可。
三、使用最小花费爬楼梯
数组的每个下标作为⼀个阶梯,第 i 个阶梯对应着⼀个⾮负数的体⼒花费值 cost[i](下标从
0 开始)。
每当你爬上⼀个阶梯你都要花费对应的体⼒值,⼀旦⽀付了相应的体⼒值,你就可以选择向
上爬⼀个阶梯或者爬两个阶梯。
请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初
始阶梯。⽰例 1:
输⼊:cost = [10, 15, 20]
输出:15
解释:最低花费是从 cost[1] 开始,然后⾛两步即可到阶梯顶,⼀共花费 15 。
⽰例 2:
输⼊:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出:6
解释:最低花费⽅式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,⼀共花费 6 。
提⽰:
cost 的长度范围是 [2, 1000]。
cost[i] 将会是⼀个整型数据,范围为 [0, 999] 。
public static int minCost(int[] cost) {
//1、确定dp数组(dp[i]代表到i层所花费最小)
int[] dp = new int[cost.length];
//2、确定转移方程
// dp[i] = min(dp[i-1],dp[i-2]) + cost[i];
//3、初始化数组
dp[0] = cost[0];
dp[1] = cost[1];
//4、确定遍历顺序
for (int i = 2; i < cost.length; i++) {
//5、推导方程
dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];
}
return Math.min(dp[cost.length - 1], dp[cost.length - 2]);
}
我的思路
- 本题求到达n阶的楼顶需要的最小花费,所以dp[i]表示到达i阶所需要的最小花费
- 到达n阶可能由n-1到达也可能从n-2到达,求最小花费则min(dp[n-1],dp[n-2])还要加上到达i阶所虚的体力,则最终的方程为dp[n] = min(dp[n-1],dp[n-2])+cost[n]
- 到达第0阶所需要的体力为cost[0],到达第1阶所需要的体力也只有一种可能即为cost[1]
- 到达顶层之后不会再往上爬了所以最后一次就不用消费体力了,则遍历的深度为n-1
- 推导的时候待入转移方程即可,最后返回值的时候注意登顶之后不会消耗体力所以只需要算出登顶前最小的体力消耗即可
四、不同路径
⼀个机器⼈位于⼀个 m x n ⽹格的左上⾓ (起始点在下图中标记为 “Start” )。 机器⼈每次只能向下或者向右移动⼀步。机器⼈试图达到⽹格的右下⾓(在下图中标记为 “Finish” )。 问总共有多少条不同的路径。
public static int uniquePaths2(int m, int n) {
//1、确定dp数组(代表到达dp[i][j]这个点所需要一共多少路径)
int[][] dp = new int[m][n];
//2、确定状态转移方程
// dp[i][j] = dp[i-1][j]+dp[i][j-1];
//3、初始化值
for (int i = 0; i < m; i++) {
dp[i][0] = 1;
}
for (int j = 0; j < n; j++) {
dp[0][j] = 1;
}
//遍历
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
- 求到达右下角的不同路径,所以dp代表的是到达指定位置的不同路径即dp[i][j]就是代表到达i,j。这个坐标的不同路径。
- 到达某个坐标可能从上方也可能从左边,则转移方程则为dp[i][j] = dp[i][j-1] + dp[i-1][j]。
- 如何初始化时这个题目比较重要的一环,当一直往右走,或者一直往下走都只有一个路径,所以此时设置其中一个变量为0通过循环进行初始化。
- 因为当两个变量从0开始时都已经初始化过了,所以这里遍历的时候从1开始,然后深度时小于指定点的坐标。
- 推导时带入指定方程即可。
五、不同路径Ⅱ
⼀个机器⼈位于⼀个 m x n ⽹格的左上⾓ (起始点在下图中标记为“Start” )。 机器⼈每次只能向下或者向右移动⼀步。机器⼈试图达到⽹格的右下⾓(在下图中标记为 “Finish”)。 现在考虑⽹格中有障碍物。那么从左上⾓到右下⾓将会有多少条不同的路径?
public static int uniquePathsII(int[][] obstacleGrid) {
//1、确定dp
int[][] dp = new int[obstacleGrid.length][obstacleGrid[0].length];
//2、dp方程
// dp[i][j] = dp[i-1][j] + dp[i][j-1];
//3、初始化(如果有阻碍则这一整条路上之后所有的点都无法到达)
for (int i = 0; i < obstacleGrid.length; i++) {
if (obstacleGrid[i][0] != 1) {
dp[i][0] = 1;
} else {
break;
}
}
for (int j = 0; j < obstacleGrid[0].length; j++) {
if (obstacleGrid[0][j] != 1) {
dp[0][j] = 1;
} else {
break;
}
}
//遍历
for (int i = 1; i < obstacleGrid.length; i++) {
for (int j = 1; j < obstacleGrid[0].length; j++) {
//判断该点是否可到达
if (obstacleGrid[i][j] == 1) {
dp[i][j] = 0;
} else {
//推导
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
}
return dp[obstacleGrid.length - 1][obstacleGrid[0].length - 1];
}
这道题目跟上一道题目很类似,主要区别在于其有一个障碍,五步法分析的方式都差不多,主要区别在于初始化和推导
当一条其中一个坐标为0时,循环初始化的过程中遇到了障碍则障碍所在的坐标已经该障碍之后的坐标点均无法到达所以路径为0
遍历种推导的过程也是同一个道理,当该坐标点遇到障碍的时候,此时到达该点的路径即为0