动态规划
动态规划
动态规划是一种解决多阶段决策问题的优化方法,把多阶段转化为一系列但阶段问题,利用各阶段之间的关系,逐个求解。
1.动态规划问题的解法
确定遍历顺序:逆序遍历、顺序遍历
2.动态规划问题的性质
能采用动态规划求解的问题的一般要具有3个性质:
- 最优性原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优性原理。
- 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
- 有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)。
3.动态规划问题“五部曲"
- 确定dp数组以及下标含义
- 确定递推公式
- 初始化dp数组
- 确定遍历顺序
- 举例推导pd数组
LeetCode70.爬楼梯
1. 确定dp数组以及下标含义
dp[i]:爬到第i层楼,有dp[i]种方法
2. 确定递推公式
dp[i]可以有两个方向推导出来
首先是dp[i - 1],上到第i-1层楼梯,有dp[i-1]种方法,那么再跳一个台阶就是dp[i]
还有就是dp[i - 2],上到第i-2层楼梯,有dp[i-2]种方法,那么再跳两个台阶就是dp[i]
所以dp[i] = dp[i - 1] + dp[i - 2]
3. 初始化dp数组
dp[1] = 1
dp[2] = 2
4. 确定遍历顺序
顺序遍历
5. 举例推导pd数组
1 2 3 4 5 8 13(斐波那契数列的变种)
class Solution {
public int climbStairs(int n) {
if (n <= 2) return n;
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i ++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
LeetCode746. 使用最小花费爬楼梯
1.确定dp数组以及下标含义
dp[i]表示爬上第i个台阶需要支付的最低费用
2.确定递推公式
dp[i]可以由dp[i - 2]爬2阶到达
dp[i]可以由dp[i - 1]爬1阶到达
dp[i] = min(dp[i - 2] + cost[i - 2], dp[i - 1] + cost[i - 1])
3.初始化dp数组
dp[0] = dp[1] = 0
4.确定遍历顺序
顺序遍历
5.举例推导dp数组
求dp[cost.length]
class Solution {
public int minCostClimbingStairs(int[] cost) {
int[] dp = new int[cost.length + 1];
dp[0] = 0;
dp[1] = 0;
for (int i = 2; i <= cost.length; i ++) {
dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
}
return dp[cost.length];
}
}
LeetCode62. 不同路径
1.确定dp数组以及下标含义
dp[i][j]:表示从(0,0)到(i,j)有dp[i][j]条路径
2.确定递推公式
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
3.初始化dp数组
处理边界
for(int i = 0; i < m; i ++) dp[i][0] = 1;
for(int j = 0; j < n; j ++) dp[0][j] = 1;
4.确定遍历顺序
顺序遍历
5.举例推导dp数组
求dp[m - 1][n - 1]
class Solution {
public int uniquePaths(int m, int n) {
//step1: dp[i][j]表示从(0,0)出发到(i,j)有dp[i][j]条不同的路径
int[][] dp = new int[m][n];
//step2: 初始化dp数组
for (int j = 0 ; j <= n - 1; j ++) dp[0][j] = 1;
for (int i = 0; i <= m - 1; i ++) dp[i][0] = 1;
//step3: 递推公式
for (int i = 1; i <= m - 1; i ++) {
for (int j = 1; j <= n - 1; j ++) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
}
LeetCode63. 不同路径II
1.确定dp数组以及下标含义
dp[i][j]:表示从(0,0)到(i,j)有dp[i][j]条路径
2.确定递推公式
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
若遇到障碍物,则把此障碍物初始化为0
3.初始化dp数组
处理边界
for(int i = 0; i < m; i ++) dp[i][0] = 1;
for(int j = 0; j < n; j ++) dp[0][j] = 1;
若在边界遇到障碍物,则边界上的障碍物和障碍物后面的单元格都初始化为0
4.确定遍历顺序
顺序遍历
5.举例推导dp数组
求dp[m - 1][n - 1]
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
// 获取二维数组的行数和列数
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] dp = new int[m][n];
// 初始化dp数组
for (int i = 0; i < m; i ++) {
//如果[i][0]是障碍物,那障碍物以及障碍物以后的单元格都初始化为0
if (obstacleGrid[i][0] == 1) {
break;
}
dp[i][0] = 1; // 否则初始化为1
}
for (int j = 0; j < n; j ++) {
//如果[0][j]是障碍物,那障碍物以及障碍物以后的单元格都初始化为0
if (obstacleGrid[0][j] == 1) {
break;
}
dp[0][j] = 1;
}
for (int i = 1; i < m; i ++) {
for (int j = 1; j < n; j ++) {
if (obstacleGrid[i][j] != 1) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
} else {
dp[i][j] = 0;
}
}
}
return dp[m - 1][n - 1];
}
}
本文作者:Ac_c0mpany丶
本文链接:https://www.cnblogs.com/keyongkang/p/16337339.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步