一、题目描述
Given a triangle, find the minimum path sum from top to bottom.
Each step you may move to adjacent numbers on the row below.
For example, given the following triangle
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).
Note:
Bonus point if you are able to do this using only O(n) extra space,
where n is the total number of rows in the triangle.
二、题解
方法一:递归
递归三部曲:
- 结束时机:到达最后一层的某个点,自然返回该点权值。
- 本层递归的责任:寻找向下走和向右下走的最小权值。
- 返回值:本层格子的权值 + 上述最小权值。
int R;
public int minimumTotal(List<List<Integer>> triangle) {
R = triangle.size();
return cost(0, 0, triangle);
}
private int cost(int level, int col, List<List<Integer>> tri) {
if (level == R - 1)
return tri.get(level).get(col);
int goDown = cost(level + 1, col, tri);
int goRight = cost(level + 1, col + 1, tri);
return tri.get(level).get(col) + Math.min(goDown, goRight);
}
复杂度分析
- 时间复杂度: ,N 为三角形的格子数量。
- 空间复杂度: ,每一个格子,就是每一个递归树的结点,每个结点都会有两个分叉。
方法二:记忆化搜索
还是递归,只不过用了一个二维的 memo 数组来记录每一个格子的最小权值。进而减少无用的重复计算。
int R;
int[][] memo = null;
public int minimumTotal(List<List<Integer>> triangle) {
R = triangle.size();
memo = new int[R][R];
return search(0, 0, triangle);
}
private int search(int level, int col, List<List<Integer>> tri) {
if (level == R - 1)
return tri.get(level).get(col);
if (memo[level][col] == 0) {
memo[level][col] = tri.get(level).get(col) +
Math.min(search(level + 1, col, tri), search(level + 1, col + 1, tri));
}
return memo[level][col];
}
复杂度分析
- 时间复杂度: ,对于每一个格子只需遍历一遍。
- 空间复杂度: ,
方法三:dp(自顶向下)
为了方便,使用缩写 tri 代替 triangle 了。还是先写出动态规划的三部曲:
- 初始状态:
dp[0][0] = tri[0][0]
- 状态定义:
dp[i][j]
的值代表直到走到(i, j)
的最小路径和。 - 转移方程:
- 起点(第二行):
dp[i][j] = dp[i-1][j] + tri[i][j]
- 每行的最后一列:即
i = j
时,dp[i][j] = dp[i-1][j-1] + tri[i][j]
- 非三角形边界:
dp[i][j] = min(dp[i-1][j], dp[i-1][j-1]) + tri[i][j]
- 起点(第二行):
public int minimumTotal(List<List<Integer>> triangle) {
int[][] dp = new int[R][R];
int R = triangle.size();
int C = triangle.get(R - 1).size();
dp[0][0] = triangle.get(0).get(0);
for (int i = 1; i < R; i++)
for (int j = 0; j <= i; j++) {
if (j == 0) //第一行
dp[i][j] = dp[i-1][j] + triangle.get(i).get(j);
else if (i == j)//每行的最后一列
dp[i][j] = dp[i-1][j-1] + triangle.get(i).get(j);
else //非边界
dp[i][j] = Math.min(dp[i-1][j-1], dp[i-1][j]) + triangle.get(i).get(j);
}
final int k = R - 1;
int min = Integer.MAX_VALUE;
for (int i = 0; i < R; i++) {
if (dp[k][i] < min)
min = dp[k][i];
}
return min;
}
复杂度分析
- 时间复杂度: ,N 为三角形的格子数。
- 空间复杂度: ,
附:空间压缩
如果理解了上面的代码,你会发现每次计算子问题时,都会用到 dp[i-1][j]
和 dp[i-1][j-1]
而已,这完全可以用两个变量来将二维 dp 数组降到一维。
int dp_i1j 代表 dp[i-1][j]
int dp_i1j1 代表 dp[i-1][j-1]
你会发现对于每一行的每一次遍历只是列坐标在变化,所以这个做法是可行的。
public int minimumTotal2(List<List<Integer>> triangle) {
int R = triangle.size();
int C = triangle.get(R - 1).size();
int[] dp = new int[R];
dp[0] = triangle.get(0).get(0);
int dp_i1j; //dp[i-1][j]
int dp_i1j1 = 0;//dp[i-1][j-1]
for (int i = 1; i < R; i++)
for (int j = 0; j <= i; j++) {
dp_i1j = dp[j];
if (j == 0)
dp[j] = triangle.get(i).get(j) + dp_i1j;
else if (i == j)
dp[j] = triangle.get(i).get(j) + dp_i1j1;
else
dp[j] = Math.min(dp_i1j, dp_i1j1) + triangle.get(i).get(j);
dp_i1j1 = dp_i1j;
}
int min = Integer.MAX_VALUE;
for (int i = 0; i < R; i++) {
if (dp[i] < min)
min = dp[i];
}
return min;
}
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
方法四:dp(自底向上)
还是先写出动态规划的三部曲,dp 的方向代表行走的方向:
- 初始状态:
dp[R-1][i] = tri[R-1][i]
,即 dp 的最后一行初始化为 tri 的最后一行。 - 状态定义:
dp[i][j]
的值代表直到走到(i, j)
的最小路径和。 - 转移方程:
dp[i][j] = min(dp[i+1][j], dp[i+1][j+1]) + tri[i][j]
public int minimumTotal3(List<List<Integer>> triangle) {
int R = triangle.size();
int C = triangle.get(R - 1).size();
int[][] dp = new int[R][R];
dp[0][0] = triangle.get(0).get(0);
//初始化最后一行
for (int i = 0; i < triangle.get(R - 1).size(); i++) {
dp[R-1][i] = triangle.get(R-1).get(i);
}
for (int i = R - 2; i >= 0; i--) {
List<Integer> oneRow = triangle.get(i);
for (int j = 0; j < oneRow.size(); j++) {
dp[i][j] = oneRow.get(j) + Math.min(dp[i+1][j+1], dp[i+1][j]);
}
}
return dp[0][0];
}
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
附:空间压缩
dp[j]
中记录了求第 i (0<= i <=R-2)
行时的最小路径总和。也是一样,遍历的每一行都是列下标在变化。
public int minimumTotal(List<List<Integer>> triangle) {
int R = triangle.size();
int C = triangle.get(R - 1).size();
int[] dp = new int[R];
for (int i = 0; i < triangle.get(R - 1).size(); i++) {
dp[i] = triangle.get(R-1).get(i);
}
for (int i = R - 2; i >= 0; i--) {
List<Integer> oneRow = triangle.get(i);
for (int j = 0; j < oneRow.size(); j++) {
dp[j] = Math.min(dp[j], dp[j+1]) + oneRow.get(j);
}
}
return dp[0];
}
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,