三角矩阵之类的题主要就是求最小距离,之后遇见有关于矩阵、网格、字符串间的比较问题,用一维动归难以解决或者难以理解的情况下,可以考虑着用二维动归,接下来就用三角矩阵案例来介绍一下动归问题的各种思想。
牛客链接
题目:给出一个三角形,计算从三角形顶部到底部的最小路径和,每一步都可以移动到下面一行相邻的数字,例如,给出的三角形如下:
[[20],[30,40],[60,50,70],[40,10,80,30]]
最小的从顶部到底部的路径和是20 + 30 + 50 + 10 = 110。
自顶向下
分析思路:
想要用自顶向下的方法求最短路径之和就需要求从坐标(0, 0)到(i,j)的最小路径之和,最后在三角矩阵的最后一行必然都是到达该点的最短路径和,只需要遍历最后一行,找到其中最小的路径之和就解决了问题
每一个元素都有两种移动路径,坐标(i,j)可以移动到坐标(i+1,j)和(i+1,j+1)处
移动到坐标(i,j)处有出现三种可能
- 坐标中的列坐标为0时(i,0),移动到该点的坐标只可能是坐标(i-1, 0)
- 坐标中行坐标和列坐标相同时(例如(1, 1)之类的),移动到该坐标的只可能是坐标(i-1,j-1)
- 其他情况下,移动到该坐标都有两个坐标(i-1,j-1)和(i-1,j)
那么在求最短路径和时只需要将该坐标的值加上到达该坐标前的最短路径和
将状态设置为F(i,j) :从(0,0)坐标到(i,j)坐标的最短路径和
例如
F(0,0)= 20;
F(1,0)= F(0,0)+ 30 = 50 ;F(1,1)= F(0,0)+ 40 = 60;
F(2,1) = min(F(1,0),F(1,1))+ 50 = 100;
综上所述:
- 状态定义F(i,j):从(0,0)坐标到(i,j)坐标的最短路径和
- 状态间的转移方程定义F(i,j):
- min(F(i-1,j-1),F(i-1,j)) + path[i][j] (0 < j < i)
- F(i-1,0) + path[i][0] (j == 0)
- F(i-1,j-1) + path[i][j] (i == j)
- 状态的初始化F(0,0) = path[0][0]
- 返回结果min(F(row-1,j))
import java.util.*;
public class Solution {
public int minimumTotal(ArrayList<ArrayList<Integer>> triangle) {
if(triangle==null||triangle.isEmpty()) return 0;
int row = triangle.size();
int[][] path = new int[row][row];
path[0][0] = triangle.get(0).get(0);
for(int i = 1;i < row;i ++) {
for(int j = 0;j <= i;j ++) {
if(j == 0) {
path[i][j] = path[i-1][j] + triangle.get(i).get(j);
} else if(i == j) {
path[i][j] = path[i-1][j-1] + triangle.get(i).get(j);
}else {
path[i][j] = Math.min(path[i-1][j],path[i-1][j-1]) + triangle.get(i).get(j);
}
}
}
int min = path[row-1][0];
for(int j = 1;j < row;j ++) {
if(path[row-1][j] < min)
min = path[row-1][j];
}
return min;
}
}
自底向上
事实上,比起自顶向下,自底向上的方法会更加简单,也更方便理解
分析思路:
自底向上的思路,设置的状态是从坐标(i,j)到最后一行的最短距离,这就需要三角矩阵的最后一行不变(初始化条件)。另外,三角矩阵的每个元素(除了最后一行)都有两种移动路径,就可以避免了上一种思路中转移方程需要考虑各种情况的麻烦,最后坐标(0,0)处的数值就是三角矩阵最短路径和。
例如
F(3,0)= 40;F(3,1)=10;
F(2,0) = min(F(3,0),F(3,1))+ 60 = 70;
综上所述:
-
状态定义F(i,j):从(i,j)坐标到最后一行的最短路径和 -
状态间的转移方程定义F(i,j):min(F(i+1,j),F(i+1,j+1)) + path[i][j] (0 <= j <= i) -
状态的初始化F(row-1,0) = path[row-1][j] -
返回结果F(0,0)
import java.util.*;
public class Solution {
public int minimumTotal(ArrayList<ArrayList<Integer>> triangle) {
if(triangle==null||triangle.isEmpty()) return 0;
int row = triangle.size();
int[][] path = new int[row][row];
for(int j = 0;j < row;j++) {
path[row -1][j] = triangle.get(row -1).get(j);
}
for(int i = row-2;i >= 0;i--) {
for(int j = 0;j <= i;j++) {
path[i][j] = Math.min(path[i+1][j],path[i+1][j+1]) + triangle.get(i).get(j);
}
}
return path[0][0];
}
}
O(N)空间
事实上,可以只用O(N)的额外的空间来完成这项工作,其中N是三角形中的行总数。
主要的思路和自底向上的思路是差不多的,只是存储的方式出现了差别,不再将每个坐标的状态单独放在数组里。这里选择用大小为三角矩阵的行数的一维数组来存状态。
import java.util.*;
public class Solution {
public int minimumTotal(ArrayList<ArrayList<Integer>> triangle) {
if(triangle==null||triangle.size()==0){
return 0;
}
int row=triangle.size();
int[]path=new int[row];
for(int j=0;j<row;j++){
path[j]=triangle.get(row-1).get(j);
}
for(int i=row-2;i>=0;i--){
for(int j=0;j<=i;j++){
path[j]=triangle.get(i).get(j)+Math.min(path[j],path[j+1]);
}
}
return path[0];
}
}
完!
|