動態規划----數字三角形問題


題目:

  在數字三角形中尋找一條從頂部到底邊的路徑,使得路徑上所經過的數字之和最大。路徑上的每一步都只能往左下或 右下走。只需要求出這個最大和即可,不必給出具體路徑。三角形的行數大於1小於等於100,數字為 0 - 99。
  輸入格式:

5 //表示三角形的行數 接下來輸入三角形
    7
   3 8
  8 1 0
 2 7 4 4
4 5 2 6 5

  要求輸出最大和。

思路分析:

  這里的遞歸和記憶型遞歸都很容易理解,遞歸和記憶型遞歸都是自頂向下,動規則是自底向上,由小規模向上推。

  這里寫dp方程的時候有一個思路,那就是依賴誰先求誰,這里依賴的是下一步的最大值,於是我們就可以先求出下一步的最大值,然后往上遞推求出我們最終需要的值。我們可以用二維數組存儲從最后一行開始每個路徑的結果。

  那有些題目有空間大小的限制,這里就涉及到另一種技巧,滾動數組,滾動數組就是把本身需要保留的結果,但是利用過后直接用下一次的結果給覆蓋掉,這樣的一個數組就是滾動數組。

  滾動數組是DP中的一種編程思想。簡單的理解就是讓數組滾動起來,每次都使用固定的幾個存儲空間,來達到壓縮,節省存儲空間的作用。起到優化空間,主要應用在遞推或動態規划中(如01背包問題)。因為DP題目是一個自底向上的擴展過程,我們常常需要用到的是連續的解,前面的解往往可以舍去。所以用滾動數組優化是很有效的。利用滾動數組的話在N很大的情況下可以達到壓縮存儲的作用

代碼:

import java.time.Duration;
import java.time.Instant;
import java.util.Scanner;

public class 數字三角形 {
	public static void main(String[] args) {
//		 int[][] triangle = {
//		 {7},
//		 {3, 8},
//		 {8, 1, 0},
//		 {2, 7, 4, 4},
//		 {4, 5, 2, 6, 5},
//		 {4, 5, 2, 6, 5, 7},
//		 {4, 13, 12, 88, 6, 6, 5},
//		 {3, 8, 7, 11, 9, 22, 66, 3},
//		 };
//		 Instant now = Instant.now();
//		 System.out.println(maxSumUsingRecursive(triangle, 0, 0));
//		 System.out.println("持續時間為:" +
//				 Duration.ofMillis(Instant.now().toEpochMilli() -
//				 now.toEpochMilli()).getSeconds());
//		 
//		 
//		 now = Instant.now();
//		 System.out.println(maxSumUsingMemory(triangle, 0, 0, new int[8][8]));
//		 System.out.println("持續時間為:" +
//				 Duration.ofMillis(Instant.now().toEpochMilli() -
//				 now.toEpochMilli()).getSeconds());
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int[][] triangle = new int[n][];
		for (int i = 0; i < n; i++) {
			triangle[i] = new int[i + 1];
			for (int j = 0; j < i + 1; j++) {
				triangle[i][j] = sc.nextInt();
			}
		}
		System.out.println(maxSumUsingDp(triangle, 0, 0));
	}

	/**
	 * @param triangle 數字三角形           
	 * @param i 起點行號            
	 * @param j 起點列號         
	 * @return 計算出的最大和
	 */
	public static int maxSumUsingRecursive(int[][] triangle, int i, int j) {
		int rowIndex = triangle.length;
		if (i == rowIndex - 1) {
			return triangle[i][j];
		} else {
			// 頂點的值+max(左側支路的最大值,右側支路的最大值)
			return triangle[i][j]
					+ Math.max(maxSumUsingRecursive(triangle, i + 1, j), maxSumUsingRecursive(triangle, i + 1, j + 1));
		}
	}

	/**
	 * 記憶型遞歸
	 */
	public static int maxSumUsingMemory(int[][] triangle, int i, int j, int[][] map) {
		int rowIndex = triangle.length;
		int value = triangle[i][j];
		if (i == rowIndex - 1) {
		} else {
			// 緩存有值,便不遞歸
			int v1 = map[i + 1][j];
			if (v1 == 0) {
				v1 = maxSumUsingMemory(triangle, i + 1, j, map);
			}
			// 緩存有值,便不遞歸
			int v2 = map[i + 1][j + 1];
			if (v2 == 0) {
				v2 = maxSumUsingMemory(triangle, i + 1, j + 1, map);
			}
			value = value + Math.max(v1, v2);
		}
		// 放入緩存
		map[i][j] = value;
		return value;
	}

	// 滾動數組
	public static int maxSumUsingDp(int[][] triangle, int i, int j) {
		int rowCount = triangle.length;// 行數
		int columnCount = triangle[rowCount - 1].length;// 最后一行的列數
		int[] dp = new int[columnCount];
		for (int k = 0; k < columnCount; k++) {
			dp[k] = triangle[rowCount - 1][k];// 初始化最后一行
		}

		for (int k = rowCount - 2; k >= 0; k--) {
			for (int l = 0; l <= k; l++) {
				dp[l] = triangle[k][l] + Math.max(dp[l], dp[l + 1]);
			}
		}
		return dp[0];
	}
}

結果:

    

  路徑為:7 3 8 7 5。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM