67.从递归入手二维动态规划

本节内容

本节课:
讲解从递归到二维动态规划的过程
讲解二维动态规划的空间压缩技巧
讲解哪些递归不适合或者说没有必要改成动态规划

下节课:
直接从动态规划的定义入手,来见识更多二维动态规划问题

如何解决

尝试函数有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<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值