题目描述
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。相邻的结点,在这里指的是下标与上一层结点下标相同或者等于上一层结点下标 + 1 的两个结点。
例如,给定三角形:
1 2 3 4 5 6
| [ [2], [3, 4], [6, 5, 7], [4, 1, 8, 3] ]
|
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
递归
给定的三角形的行数为 n,并且第 i 行(从 0 开始编号)包含了 i + 1
个数。如果将每一行的左端对齐,那么会形成一个等腰直角三角形,如下所示:
1 2 3 4
| [2], [3, 4], [6, 5, 7], [4, 1, 8, 3]
|
这样,我们可以从顶点自上而下递归处理最小路径和,首先从顶点开始,他的最小路径和是顶点的数2加上下面的以3为顶点的最短路径和,或者顶点2加上下面以4为顶点的最短路径和,取其中的最小值,并不断递归处理。这里需要注意的一点是,对于第二行的顶点3和4,计算他们下一层的最短路径和的时候,会计算两遍第三行的以5为顶点的三角形最短路径和。所以我们在递归的同时,记录下计算过的路径和,防止重复计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public int minimumTotal(List<List<Integer>> triangle) { if (triangle == null || triangle.isEmpty() || triangle.get(0).isEmpty()) { return 0; } int[][] minValue = new int[triangle.size() + 1][triangle.size() + 1]; for (int i = 0; i < minValue.length; i++) { Arrays.fill(minValue[i], Integer.MAX_VALUE); } int value = triangle.get(0).get(0); int leftValue = value + minimumTotal(triangle, 1, 0, minValue); int rightValue = value + minimumTotal(triangle, 1, 1, minValue); return Math.min(leftValue, rightValue); }
public int minimumTotal(List<List<Integer>> triangle, int row, int column, int[][] minValue) { if (row >= triangle.size() || column >= triangle.get(row).size()) { return 0; } int value = triangle.get(row).get(column); int leftValue = value + (minValue[row + 1][column] < Integer.MAX_VALUE ? minValue[row + 1][column] : minimumTotal(triangle, row + 1, column, minValue)); int rightValue = value + (minValue[row + 1][column + 1] < Integer.MAX_VALUE ? minValue[row + 1][column + 1] : minimumTotal(triangle, row + 1, column + 1, minValue)); return minValue[row][column] = Math.min(leftValue, rightValue); }
|
复杂度分析
- 时间复杂度:O(n²),其中 n 是三角形的行数。
- 空间复杂度:O(n²),我们需要一个 n ∗ n 的二维数组存放记录的值。
动态规划-自顶向下
我们用 f[i][j]
表示从三角形顶部走到位置 (i, j)
的最小路径和。这里的位置 (i, j)
指的是三角形中第 i 行第 j 列(均从 0 开始编号)的位置。由于每一步只能移动到下一行「相邻的节点」上,因此要想走到位置 (i, j)
,上一步就只能在位置 (i - 1, j - 1)
或者位置 (i - 1, j)
。我们在这两个位置中选择一个路径和较小的来进行转移,状态转移方程为:
f[i][j] = min(f[i − 1][j − 1], f[i − 1][j]) + c[i][j]
其中 c[i][j]
表示位置 (i, j)
对应的元素值。
注意第 i 行有 i + 1
个元素,它们对应的 j 的范围为 [0, i]
。当 j = 0
或 j = i
时,上述状态转移方程中有一些项是没有意义的。例如当 j = 0
时,f[i − 1][j − 1]
没有意义,因此状态转移方程为:
f[i][0] = f[i − 1][0] + c[i][0]
即当我们在第 i 行的最左侧时,我们只能从第 i − 1
行的最左侧移动过来。当 j = i
时,f[i - 1][j]
没有意义,因此状态转移方程为:
f[i][i] = f[i − 1][i − 1] + c[i][i]
即当我们在第 i 行的最右侧时,我们只能从第 i − 1
行的最右侧移动过来。
最终的答案即为 f[n − 1][0]
到 f[n − 1][n − 1]
中的最小值,其中 n 是三角形的行数。
边界条件为:
f[0][0] = c[0][0]
即在三角形的顶部时,最小路径和就等于对应位置的元素值。这样一来,我们从 1 开始递增地枚举 i,并在 [0, i]
的范围内递增地枚举 j,就可以完成所有状态的计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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++) { 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 minTotal = dp[n - 1][0]; for (int i = 1; i < n; ++i) { minTotal = Math.min(minTotal, dp[n - 1][i]); } return minTotal; }
|
复杂度分析
- 时间复杂度:O(n²),其中 n 是三角形的行数。
- 空间复杂度:O(n²),我们需要一个 n ∗ n 的二维数组存放所有的状态。
动态规划-自底向上
跟上述的方法一致,只不过我们采用自底向上的方法,状态转移方程为:
dp[i][j] = min(dp[i + 1][j], dp[i + 1][j + 1]) + c[i][j]
1 2 3 4 5 6 7 8 9 10 11
| public int minimumTotal(List<List<Integer>> triangle) { int n = triangle.size(); int[][] dp = new int[n + 1][n + 1]; for (int i = n - 1; i >= 0; i--) { for (int j = 0; j <= i; j++) { dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j); } } return dp[0][0]; }
|
复杂度分析
- 时间复杂度:O(n²),其中 n 是三角形的行数。
- 空间复杂度:O(n²),我们需要一个 n ∗ n 的二维数组存放所有的状态。
动态规划-空间优化
在上述代码中,我们定义了一个 N 行 N 列 的 dp 数组(N 是三角形的行数)。但是在实际递推中我们发现,计算 dp[i][j]
时,只用到了下一行的 dp[i + 1][j]
和 dp[i + 1][j + 1]
。因此 dp 数组不需要定义 N 行,只要定义 1 行就行。所以我们稍微修改一下上述代码,将 i 所在的维度去掉(如下),就可以将 O(N²) 的空间复杂度优化成 O(N)。
1 2 3 4 5 6 7 8 9 10
| public int minimumTotal(List<List<Integer>> triangle) { int n = triangle.size(); int[] dp = new int[n + 1]; for (int i = n - 1; i >= 0; i--) { for (int j = 0; j <= i; j++) { dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j); } } return dp[0]; }
|
复杂度分析
- 时间复杂度:O(n²),其中 n 是三角形的行数。
- 空间复杂度:O(n),我们需要一个 n 的一维数组存放所有的状态。
来源
三角形最小路径和 | 力扣(LeetCode)
三角形最小路径和 | 题解(LeetCode)