给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标与上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。
例如,给定三角形:
[ [2], [3,4], [6,5,7], [4,1,8,3] ]
自顶向下的最小路径和为
11
(即,2 + 3 + 5 + 1 = 11)。
集合:从起点到终点的所有路径组成的集合
状态表示:
dp[i][j]
,表示从起点到第i层第j列的所有路径组成的集合中,路径值最小的属性:计算出 从起点到第i层第j列的所有路径组成的集合中,路径值最小的
状态转移:
f ( i , j ) = { f ( 0 , 0 ) i = 0 , j = 0 f ( i − 1 , 0 ) + n u m s [ i ] [ j ] 0 < i ≤ n − 1 , j = 0 m i n ( f ( i − 1 , j − 1 ) , f ( i − 1 , j ) ) + n u m s [ i ] [ j ] 0 < i ≤ n − 1 , 0 < j ≤ i f ( i − i , i − 1 ) + n u m s [ i ] [ i ] i = n − 1 f(i,j)=\begin{cases} f(0,0) \quad i=0,j=0 \\ f(i-1,0) + nums[i][j] \quad 0f(i,j)=⎩⎪⎪⎪⎨⎪⎪⎪⎧f(0,0)i=0,j=0f(i−1,0)+nums[i][j]0<i≤n−1,j=0min(f(i−1,j−1),f(i−1,j))+nums[i][j]0<i≤n−1,0<j≤if(i−i,i−1)+nums[i][i]i=n−1
代码一:
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
int[][] dp = new int[n][n];
dp[0][0] = triangle.get(0).get(0);
for (int i = 1; i < n; i++) {
for (int j = 0; j <= i; j++) {
dp[i][j] = Integer.MAX_VALUE;
// 只有当j大于0时(不在第一列),才能从左上角下来
// 此判断包含了j等于i(在最后一列)
if (j > 0) {
dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1] + triangle.get(i).get(j));
}
// 只有当j小于i时(不在最后一列),才能从正上方下来
// 此判断包含了j等于0,(在第一列)
if (j < i) {
dp[i][j] = Math.min(dp[i][j], dp[i - 1][j] + triangle.get(i).get(j));
}
}
}
int result = dp[n - 1][0];
for (int i = 1; i < n; i++) {
result = Math.min(result, dp[n - 1][i]);
}
return result;
}
}
代码二:
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
// 开辟一个dp数组,保存中间计算结果
// dp[i][j],表示从起点走到第i行第j列的左右路径中,最小的路径值
// dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]
// 当j等于0时(第一列),只能从dp[i-1][0]下来
// 当j等于i时(最后一列),只能dp[i-1][i-1]下来(最后一列)
int[][] dp = new int[n][n];
dp[0][0] = triangle.get(0).get(0);
for (int i = 1; i < n; i++) {
// 第一列只能从上一行的第一列到达
dp[i][0] = dp[i - 1][0] + triangle.get(i).get(0);
// 第一列到倒数第二列
for (int j = 1; j < i; j++) {
dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle.get(i).get(j);
}
// 最后一列只能从上一层的最后一列到达
dp[i][i] = dp[i - 1][i - 1] + triangle.get(i).get(i);
}
// 遍历最后一层,找到最小值
int result = dp[n - 1][0];
// triangle的最后一行的元素个数等于triangle的行数
// 所以这里可以从i循环到n
for (int i = 1; i < n; i++) {
result = Math.min(result, dp[n - 1][i]);
}
// 返回最小值
return result;
}
}
优化一:
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
// 使用滚动数组,来记录数据,因为遍历到第i行时,只用到了第i-1行的数据,i-1之前的数据都已经不会再被使用
int[][] dp = new int[2][n];
dp[0][0] = triangle.get(0).get(0);
for (int i = 1; i < n; i++) {
for (int j = 0; j <= i; j++) {
//(& 1)等价于 %2
dp[i & 1][j] = Integer.MAX_VALUE;
if (j > 0) {
dp[i & 1][j] = Math.min(dp[i & 1][j], dp[i - 1 & 1][j - 1] + triangle.get(i).get(j));
}
if (j < i) {
dp[i & 1][j] = Math.min(dp[i & 1][j], dp[i - 1 & 1][j] + triangle.get(i).get(j));
}
}
}
int result = dp[n - 1 & 1][0];
for (int i = 1; i < n; i++) {
result = Math.min(result, dp[n - 1 & 1][i]);
}
return result;
}
}
优化二:
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
// 开辟一个dp数组,保存中间计算结果
// dp[i][j],表示从起点走到第i行第j列的左右路径中,最小的路径值
// dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]
// 当j等于0时(第一列),只能从dp[i-1][0]下来
// 当j等于i时(最后一列),只能dp[i-1][i-1]下来(最后一列)
int[][] dp = new int[2][n];
dp[0][0] = triangle.get(0).get(0);
for (int i = 1; i < n; i++) {
// 第一列只能从上一行的第一列到达
dp[i & 1][0] = dp[i - 1 & 1][0] + triangle.get(i).get(0);
// 第一列到倒数第二列
for (int j = 1; j < i; j++) {
dp[i & 1][j] = Math.min(dp[i - 1 & 1][j - 1], dp[i - 1 & 1][j]) + triangle.get(i).get(j);
}
// 最后一列只能从上一层的最后一列到达
dp[i & 1][i] = dp[i - 1 & 1][i - 1] + triangle.get(i).get(i);
}
// 遍历最后一层,找到最小值
int result = dp[n - 1 & 1][0];
// triangle的最后一行的元素个数等于triangle的行数
// 所以这里可以从i循环到n
for (int i = 1; i < n; i++) {
result = Math.min(result, dp[n - 1 & 1][i]);
}
// 返回最小值
return result;
}
}