本节内容
本节课:
讲解从递归到二维动态规划的过程
讲解二维动态规划的空间压缩技巧
讲解哪些递归不适合或者说没有必要改成动态规划
下节课:
直接从动态规划的定义入手,来见识更多二维动态规划问题
如何解决
尝试函数有1个可变参数可以完全决定返回值,进而可以改出1维动态规划表的实现
同理
尝试函数有2个可变参数可以完全决定返回值,那么就可以改出2维动态规划的实现
一维、二维、三维甚至多维动态规划问题,大体过程都是:
写出尝试递归
记忆化搜索(从顶到底的动态规划)
严格位置依赖的动态规划(从底到顶的动态规划)
空间、时间的更多优化
动态规划表的大小:每个可变参数的可能性数量相乘
动态规划方法的时间复杂度:动态规划表的大小 * 每个格子的枚举代价
二维动态规划依然需要去整理 动态规划表的格子之间的依赖关系
找寻依赖关系,往往 通过画图来建立空间感,使其更显而易见
然后依然是 从简单格子填写到复杂格子 的过程,即严格位置依赖的动态规划(从底到顶)
二维动态规划的压缩空间技巧原理不难,会了之后千篇一律
但是不同题目依赖关系不一样,需要 很细心的画图来整理具体题目的依赖关系
最后进行空间压缩的实现
题目一:最小路径和
问题描述:
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
方法一:暴力递归
算法思想:
1.采用递归思想
2.递归终止条件,遇到(0,0)格子,返回(0,0)代价
3.递归的定义:从(0,0)到当前格子(i,j)的最小代价
4.递归每一步做的事情:比较当前格子+左边或右边的最小代价,选最小的当作这个格子的最小代价
代码如下:
// 暴力递归
public static int minPathSum1(int[][] grid) {
return f1(grid, grid.length - 1, grid[0].length - 1);
}
// 从(0,0)到(i,j)最小路径和
// 一定每次只能向右或者向下
public static int f1(int[][] grid, int i, int j) {
if (i == 0 && j == 0) {
return grid[0][0];
}
int up = Integer.MAX_VALUE;
int left = Integer.MAX_VALUE;
if (i - 1 >= 0) {
up = f1(grid, i - 1, j);
}
if (j - 1 >= 0) {
left = f1(grid, i, j - 1);
}
return grid[i][j] + Math.min(up, left);
}
方法二:记忆化搜索(傻缓存)
算法思想:
采用一个缓存表来记录代价(i,j)
代码如下:
// 记忆化搜索
public static int minPathSum2(int[][] grid) {
int n = grid.length;
int m = grid[0].length;
int[][] dp = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
dp[i][j] = -1;
}
}
return f2(grid, grid.length - 1, grid[0].length - 1, dp);
}
public static int f2(int[][] grid, int i, int j, int[][] dp) {
if (dp[i][j] != -1) {
return dp[i][j];
}
int ans;
if (i == 0 && j == 0) {
ans = grid[0][0];
} else {
int up = Integer.MAX_VALUE;
int left = Integer.MAX_VALUE;
if (i - 1 >= 0) {
up = f2(grid, i - 1, j, dp);
}
if (j - 1 >= 0) {
left = f2(grid, i, j - 1, dp);
}
ans = grid[i][j] + Math.min(up, left);
}
dp[i][j] = ans;
return ans;
}
方法三:动态规划
算法思想:
由暴力递归改动态规划
我们发现递归都是要先算左边和上面
所以dp我们先填最上行,再填最左行。最后填由从左向右一行一行填
代码如下:
// 严格位置依赖的动态规划
public static int minPathSum3(int[][] grid) {
int n = grid.length;
int m = grid[0].length;
int[][] dp = new int[n][m];
dp[0][0] = grid[0][0];
for (int i = 1; i < n; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < m; j++) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < n; i++) {
for (int j = 1; j < m; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[n - 1][m - 1];
}
方法四:动态规划+空间压缩技巧
算法思想:
使用一维数组来代替二维dp数组
代码如下:
// 严格位置依赖的动态规划 + 空间压缩技巧
public static int minPathSum4(int[][] grid) {
int n = grid.length;
int m = grid[0].length;
// 先让dp表,变成想象中的表的第0行的数据
int[] dp = new int[m];
dp[0] = grid[0][0];
for (int j = 1; j < m; j++) {
dp[j] = dp[j - 1] + grid[0][j];
}
for (int i = 1; i < n; i++) {
// i = 1,dp表变成想象中二维表的第1行的数据
// i = 2,dp表变成想象中二维表的第2行的数据
// i = 3,dp表变成想象中二维表的第3行的数据
// ...
// i = n-1,dp表变成想象中二维表的第n-1行的数据
dp[0] += grid[i][0<