1. 程式人生 > >如何將暴力遞迴改為動態規劃?

如何將暴力遞迴改為動態規劃?

暴力遞迴

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明明儲存了草稿,打開發現不見了,所以又得手擼一遍。。。難受