如何将暴力递归改为动态规划?

暴力递归

1、把问题转化为规模缩小了的同类问题的子问题
2、有明确的不需要继续进行递归的终止条件
3、有当得到了子问题的结果之后的决策过程
4、不需要记录每一个子问题的解

动态规划

1、从暴力递归中来
2、将每一个子问题的解记录下来,避免重复计算(这是动态规划优于递归的本质原因)
3、把暴力递归的过程,抽象成了状态表达
4、并且存在化简状态表达,使其更加简洁的可能

开胃小菜,先来一道题感受一下如何暴力递归:

打印一个字符串的全部子序列,包括空字符串
例如abc的全部子序列为a,b,c,ab,bc,ac,abc和空字符串(注意子序列和子串的区别,子序列可以不连续,子串必须连续,前者去掉ac就是子串)。
暴力递归思路:还是以abc为例
对于每个位置,都有要和不要两种选择,穷尽所有位置即可得到答案,如下图
在这里插入图片描述

public static void printAllSubsquence(String str) {
		char[] chs = str.toCharArray();
		String res = "";
		process(chs, 0, res);
	}

	public static void process(char[] str, int i,String res) {
		if (i == str.length) {//终止条件,当i到达最后一个位置后打印结果
			System.out.println(res);
			return;
		}
		process(str, i + 1, res);//不要当前位置的值
		process(str, i + 1, res+String.valueOf(str[i]));//要当前位置的值		
	}

运行结果(第一行是空串):
在这里插入图片描述

进入正题

给你一个二维数组,数组中的每一个数都是正数,要求从左上角走到右下角,每一步只能向右或者向下,沿途经过的数字要累加起来,返回最小的路径和。
举个例子,
1, 3, 0
2, 5, 1
7, 4, 2
很显然最小路径和=1+3+0+1+2=7

  • 遇到这种题,首先我们应该先尝试写出它的暴力递归解法(最重要的一步)
  • 其次再考虑改成动态规划

分析一下暴力递归的思路:
情况(1):当前点(i,j)来到最后一列,此时只能向下走,即走到(i+1,j)
情况(2):当前点(i,j)来到最后一行,此时只能向右走,即走到(i,j+1)
情况(3):普遍情况,当前点可以选择向右或者向下,选择其中路径和较小的一个

有了思路,代码不一会儿就刷刷刷的写好了

	public static int minPath1(int[][] matrix) {
		return process1(matrix, 0, 0);//从左上角开始
	}
	//matrix为二维数组,从(i,j)出发,到达最右下角位置的最小路径和
	public static int process1(int[][] matrix, int i, int j) {
		//终止条件,当(i,j)到达最右下角是,返回最小和
		if (i == matrix.length - 1 && j == matrix[0].length - 1) {
			return matrix[i][j];
		}
		//情况(1),当(i,j)来到最后一列,只能往下走
		if (j == matrix[0].length - 1) {
			return matrix[i][j] + process1(matrix, i + 1, j);//沿途数值累加
		}
		//情况(2),当(i,j)来到最后一行,只能往右走
		if (i == matrix.length - 1) {
			return matrix[i][j] + process1(matrix, i, j + 1);
		}
		//情况(3),选择往右或往下中较小的一个
		return matrix[i][j] + Math.min(process1(matrix, i, j + 1), process1(matrix, i + 1, j));
	}
	public static void main(String[] args){
		int[][] m = { { 1, 3, 0}, { 2,5,1 }, { 7,4,2 } };
		System.out.println("最小和是:"+minPath1(m));
	}

运行一下,果然没错哈哈哈!!!!!
在这里插入图片描述

有了暴力递归,怎么改成动态规划呢?

这里要注意,不是所有的暴力递归问题都能改成动态规划,需要两个条件
(1)暴力递归问题存在大量重复计算
重复计算很容易理解,以这道题为例,
1, 3, 0
2, 5, 1
7, 4, 2
3位置要计算0位置和5位置
2位置要计算7位置和5位置
也就是说5位置及其之后所有位置都有存在着无用的重复计算,这是暴力递归递归效率不行的本质原因
(2)该问题属于**“无后效性”问题**
所谓无后效性问题是指,不管经过什么方法到达当前位置,当前位置到其所要到的位置的值是固定的,不受之前方法的影响。以这道题为例::
若当前位置是5位置,那么它可能是1->3->5或者1->2->5,但不管是从那一条路到达5位置的,5位置到右下角的最小路径和是固定不变的,始终等于5+1+2=8。

很显然这道题可以改成动态规划,分析一下思路:

  • 怎么解决重复计算问题?第一思路是记录每一个位置到右下角的最小路径和,因此我们需要一张二维表dp,这张表与原二维表matrix一一对应,dp表中每一位置的数值就是其到右下角的最小路径和,这个时候,求左上角到右下角的最小路径和就变成了求dp表中左上角即(0,0)位置的值。
  • dp表中的值怎么求?这时就需要用到前面写的暴力递归函数了,
  • 首先看递归的终止条件,终止条件的位置的值不依赖其他位置的值,所以 dp右下角的值 = matrix右下角的值
    matrix --------> dp(x表示未知)
    1, 3, 0 --------> x, x, x
    2, 5, 1 --------> x, x, x
    7, 4, 2 --------> x, x, 2
  • 递归函数最后一列只能向下走,所以dp最后一列的值也可以求,(最后一列)倒数第二个位置 = matrix对应位置的值 + dp向下位置的值
    matrix --------> dp(x表示未知)
    1, 3, 0 --------> x,, x,, x
    2, 5, 1 --------> x,, x, ,1+2
    7, 4, 2 --------> x, ,x,, 2
    -递归函数最后一行只能向右走,所以dp最后一行的值也可以求,(最后一行)倒数第二个位置 = matrix对应位置的值 + dp向右位置的值
    matrix --------> dp(x表示未知)
    1, 3, 0 --------> x,, x,, x
    2, 5, 1 --------> x,, x, ,1+2
    7, 4, 2 --------> x, ,4+2,, 2
    -同理,dp普遍位置的值 = matrix对应位置的值 + dp向右或向下位置中较小的值
    matrix --------> dp(x表示未知)
    1, 3, 0 --------> x, x, x
    2, 5, 1 --------> x, 5+(1+2),1+2
    7, 4, 2 --------> x,4+2, 2
    最终的dp表如下:
    7,6,3
    10,8,3
    13,6,2
    (0,0)位置的值7就是我们要求的答案,动态规划代码如下:
	public static int minPath2(int[][] matrix) {
		if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) {
			return 0;
		}
		int row = matrix.length;
		int col = matrix[0].length;
		int[][] dp = new int[row][col];
		dp[row-1][col-1] = matrix[row-1][col-1];//右下角的值
		for (int i = row-2; i >= 0; i--) {//最后一列的值
			dp[i][col-1] = dp[i + 1][col-1] + matrix[i][col-1];
		}
		for (int j = col-2; j >= 0; j--) {//最后一行的值
			dp[row-1][j] = dp[row-1][j + 1] + matrix[row-1][j];
		}
		for (int i = row-2; i >= 0; i--) {//普遍位置的值
			for (int j = col-2; j >= 0; j--) {
				dp[i][j] = Math.min(dp[i + 1][j], dp[i][j + 1]) + matrix[i][j];
			}
		}
		return dp[0][0];
	}
	public static void main(String[] args){
		int[][] m = { { 1, 3, 0}, { 2,5,1 }, { 7,4,2 } };
		System.out.println("最小和是:"+minPath1(m));
		System.out.println("最小和是:"+minPath2(m));
	}

在这里插入图片描述
终于写完了…。。。mdzz明明保存了草稿,打开发现不见了,所以又得手撸一遍。。。难受

扫描二维码关注公众号,回复: 4426196 查看本文章

猜你喜欢

转载自blog.csdn.net/Felix_ar/article/details/83964391