问题描述:
在数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或者右下走,只需要求出这个最大和即可,不需要给出具体路径。
例如:
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
最大和为:30
解法一:
递归。每一个点都有两个方向选择,所以递归树是二叉树。
代码如下:
import java.util.Scanner;
public class 数字三角形 {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();//表示行数
int[][] arr = new int[n][n];
for(int i=0; i<n; i++){
for(int j=0; j<=i; j++){
arr[i][j] = scanner.nextInt();
}
}
System.out.println(dfs(arr, 0, 0));
}
/*
* 自顶向下
*/
private static int dfs(int[][] arr, int row, int col) {
if(row == arr.length-1){
return arr[row][col];
}
int v1 = arr[row][col] + dfs(arr, row+1, col);//往左下走的结果
int v2 = arr[row][col] + dfs(arr, row+1, col+1);//往右下走的结果
return Math.max(v1, v2);
}
}
解法二:
用记忆型递归,在递归的基础上进行改进,因为在递归的过程中出现重复子问题;导致多次求解同一个子问题,为了提高效率在递归的过程中进行子问题解的记录。
代码如下:
import java.util.Arrays;
import java.util.Scanner;
public class 数字三角形 {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();//表示行数
int[][] arr = new int[n][n];
int[][] vs = new int[n][n];//记录表
for(int i=0; i<n; i++){//初始化记录表
Arrays.fill(vs[i], -1);
}
for(int i=0; i<n; i++){
for(int j=0; j<=i; j++){
arr[i][j] = scanner.nextInt();
}
}
System.out.println(dfs(arr, vs, 0, 0));
}
/*
* 自顶向下
*/
private static int dfs(int[][] arr, int[][] vs, int row, int col) {
if(row == arr.length-1){
return arr[row][col];
}
if(vs[row][col]>=0){//检查是否有过记录
return vs[row][col];
}
int v1 = arr[row][col] + dfs(arr, vs, row+1, col);//往左下走的结果
int v2 = arr[row][col] + dfs(arr, vs, row+1, col+1);//往右下走的结果
vs[row][col] = Math.max(v1, v2);//返回前进行记录
return Math.max(v1, v2);
}
}
解法三:
使用动态规划,和上面的递归不同的是,动态规划dp表建立的过程是自底向上的,而递归是自顶向下的。我们可以发现当走到最后一行时(也就是到达叶子结点时),dp表的值就等于节点本身的值,所以我们可以从底部向上推导。
代码如下:
import java.util.Arrays;
import java.util.Scanner;
public class 数字三角形 {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();//表示行数
int[][] arr = new int[n][n];
int[][] dp = new int[n][n];//dp表
for(int i=0; i<n; i++){
for(int j=0; j<=i; j++){
arr[i][j] = scanner.nextInt();
}
}
System.out.println(dongGui(arr, dp));
}
/*
* 动态规划,自底向上
*/
private static int dongGui(int[][] arr, int[][] vs) {
int n = arr.length;
for(int j=0; j<n; j++){//初始化最后一行
vs[n-1][j] = arr[n-1][j];
}
for(int i=n-2; i>=0; i--){//从倒数第二行开始
for(int j=0; j<=i; j++){
int v1 = arr[i][j] + vs[i+1][j];//左下方向的和
int v2 = arr[i][j] + vs[i+1][j+1];//右下方向的和
vs[i][j] = Math.max(v1, v2);
}
}
return vs[0][0];
}
}
总结:
这题的递归和动态规划和01背包问题以及钢条分割问题很类似;只是这题在递归和建表的过程中是位置在变,前面两题是参数在变。不过算法思想都一样,这题如果想节省空间还可以使用滚动数组。因为dp表每求出一行,它的下面一行就没有用了。在建立dp表时只需要根据上一行而不是一整个表;所以可以用一个一维数组来表示dp表。