|
| 1 | +# 题目描述(中等难度) |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | +当前层只能选择下一层相邻的两个元素走,比如第 `3` 层的 `5` 只能选择第`4`层的 `1` 和 `8` ,从最上边开始,走一条路径,走到最底层最小的和是多少。 |
| 6 | + |
| 7 | +# 题目解析 |
| 8 | + |
| 9 | +先看一下 [115 题](<https://leetcode.wang/leetcode-115-Distinct-Subsequences.html>) 吧,和这道题思路方法是完全完全一样的。此外,[119 题](<https://leetcode.wang/leetcode-119-Pascal%27s-TriangleII.html>) 倒着循环优化空间复杂度也可以看一下。 |
| 10 | + |
| 11 | +这道题本质上就是动态规划,再本质一些就是更新一张二维表。 |
| 12 | + |
| 13 | + [115 题](<https://leetcode.wang/leetcode-115-Distinct-Subsequences.html>) 已经进行了详细介绍,这里就粗略的记录了。 |
| 14 | + |
| 15 | +# 解法一 递归之分支 |
| 16 | + |
| 17 | +求第 `0` 层到第 `n` 层的和最小,就是第`0`层的数字加上第`1`层到第`n`层的的最小和。 |
| 18 | + |
| 19 | +递归出口就是,第`n`层到第`n`层最小的和,就是该层的数字本身。 |
| 20 | + |
| 21 | +```java |
| 22 | +public int minimumTotal(List<List<Integer>> triangle) { |
| 23 | + return minimumTotalHelper(0, 0, triangle); |
| 24 | +} |
| 25 | + |
| 26 | +private int minimumTotalHelper(int row, int col, List<List<Integer>> triangle) { |
| 27 | + if (row == triangle.size()) { |
| 28 | + return 0; |
| 29 | + } |
| 30 | + int min = Integer.MAX_VALUE; |
| 31 | + List<Integer> cur = triangle.get(row); |
| 32 | + min = Math.min(min, cur.get(col) + minimumTotalHelper(row + 1, col, triangle)); |
| 33 | + if (col + 1 < cur.size()) { |
| 34 | + min = Math.min(min, cur.get(col + 1) + minimumTotalHelper(row + 1, col + 1, triangle)); |
| 35 | + } |
| 36 | + return min; |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +因为函数里边调用了两次自己,所以导致进行了很多重复的搜索,所以肯定会导致超时。 |
| 41 | + |
| 42 | + |
| 43 | + |
| 44 | +优化的话,就是 `Memoization` 技术,把每次的结果存起来,进入递归前先判断当前解有没有求出来。我们可以用 `HashMap` 存,也可以用二维数组存。 |
| 45 | + |
| 46 | +用 `HashMap` 的话,`key` 存字符串 `row + "@" + col`,中间之所以加一个分隔符,就是防止`row = 1,col = 23` 和 `row = 12, col = 3`,这两种情况的混淆。 |
| 47 | + |
| 48 | +```java |
| 49 | +public int minimumTotal(List<List<Integer>> triangle) { |
| 50 | + HashMap<String, Integer> map = new HashMap<>(); |
| 51 | + return minimumTotalHelper(0, 0, triangle, map); |
| 52 | +} |
| 53 | + |
| 54 | +private int minimumTotalHelper(int row, int col, List<List<Integer>> triangle, HashMap<String, Integer> map) { |
| 55 | + if (row == triangle.size()) { |
| 56 | + return 0; |
| 57 | + } |
| 58 | + String key = row + "@" + col; |
| 59 | + if (map.containsKey(key)) { |
| 60 | + return map.get(key); |
| 61 | + } |
| 62 | + int min = Integer.MAX_VALUE; |
| 63 | + List<Integer> cur = triangle.get(row); |
| 64 | + min = Math.min(min, cur.get(col) + minimumTotalHelper(row + 1, col, triangle, map)); |
| 65 | + if (col + 1 < cur.size()) { |
| 66 | + min = Math.min(min, cur.get(col + 1) + minimumTotalHelper(row + 1, col + 1, triangle, map)); |
| 67 | + } |
| 68 | + map.put(key, min); |
| 69 | + return min; |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +# 动态规划 |
| 74 | + |
| 75 | +动态规划可以自顶向下,也可以自底向上, [115 题](<https://leetcode.wang/leetcode-115-Distinct-Subsequences.html>) 主要写的是自底向上,这里写个自顶向下吧。 |
| 76 | + |
| 77 | +用一个数组 `dp[row][col]` 表示从顶部到当前位置,即第 `row` 行第 `col` 列,的最小和。 |
| 78 | + |
| 79 | +状态转移方程也很好写了。 |
| 80 | + |
| 81 | +`dp[row][col] = Min(dp[row - 1][col - 1],dp[row-1][col]), triangle[row][col] ` |
| 82 | + |
| 83 | +到当前位置有两种选择,选一个较小的,然后加上当前位置的数字即可。 |
| 84 | + |
| 85 | +```java |
| 86 | +public int minimumTotal(List<List<Integer>> triangle) { |
| 87 | + int rows = triangle.size(); |
| 88 | + int cols = triangle.get(rows - 1).size(); |
| 89 | + int[][] dp = new int[rows][cols]; |
| 90 | + dp[0][0] = triangle.get(0).get(0); |
| 91 | + for (int row = 1; row < rows; row++) { |
| 92 | + List<Integer> curRow = triangle.get(row); |
| 93 | + int col = 0; |
| 94 | + dp[row][col] = dp[row - 1][col] + curRow.get(col); |
| 95 | + col++; |
| 96 | + for (; col < curRow.size() - 1; col++) { |
| 97 | + dp[row][col] = Math.min(dp[row - 1][col - 1], dp[row - 1][col]) + curRow.get(col); |
| 98 | + } |
| 99 | + dp[row][col] = dp[row - 1][col - 1] + curRow.get(col); |
| 100 | + } |
| 101 | + int min = Integer.MAX_VALUE; |
| 102 | + for (int col = 0; col < cols; col++) { |
| 103 | + min = Math.min(min, dp[rows - 1][col]); |
| 104 | + } |
| 105 | + return min; |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +注意的地方就是把左边界和右边界的情况单独考虑,因为到达左边界和右边界只有一个位置可选。 |
| 110 | + |
| 111 | +接下来,注意到我们是一层一层的更新,更新当前层只需要上一层的信息,所以我们不需要二维数组,只需要一维数组就可以了。 |
| 112 | + |
| 113 | +另外,和 [119 题](<https://leetcode.wang/leetcode-119-Pascal%27s-TriangleII.html>) 题一样,更新`col`列的时候,会把之前`col`列的信息覆盖。当更新 `col + 1` 列的时候,旧的 `col` 列的信息已经没有了,所以我们可以采取倒着更新 `col` 的方法。 |
| 114 | + |
| 115 | +```java |
| 116 | +public int minimumTotal(List<List<Integer>> triangle) { |
| 117 | + int rows = triangle.size(); |
| 118 | + int cols = triangle.get(rows - 1).size(); |
| 119 | + int[] dp = new int[cols]; |
| 120 | + dp[0] = triangle.get(0).get(0); |
| 121 | + for (int row = 1; row < rows; row++) { |
| 122 | + List<Integer> curRow = triangle.get(row); |
| 123 | + int col = curRow.size() - 1; |
| 124 | + dp[col] = dp[col - 1] + curRow.get(col); |
| 125 | + col--; |
| 126 | + for (; col > 0; col--) { |
| 127 | + dp[col] = Math.min(dp[col - 1], dp[col]) + curRow.get(col); |
| 128 | + } |
| 129 | + |
| 130 | + dp[col] = dp[col] + curRow.get(col); |
| 131 | + } |
| 132 | + int min = Integer.MAX_VALUE; |
| 133 | + for (int col = 0; col < cols; col++) { |
| 134 | + min = Math.min(min, dp[col]); |
| 135 | + } |
| 136 | + return min; |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +另外,大家可以试一试自底向上的方法,写起来还相对简单些。 |
| 141 | + |
| 142 | +# 总 |
| 143 | + |
| 144 | +就是 [115 题](<https://leetcode.wang/leetcode-115-Distinct-Subsequences.html>) 的变形了,没有新东西,如果理解了 [115 题](<https://leetcode.wang/leetcode-115-Distinct-Subsequences.html>) ,那么这道题直接套算法就行,基本不用思考了。 |
0 commit comments