120三角形最小路径和
题目描述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 给定一个三角形,找出自顶向下的最小路径和。 每一步只能移动到下一行中相邻的结点上。
相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。
例如,给定三角形:
[ [2], [3,4], [6,5,7], [4,1,8,3] ] 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
说明:
如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,
|
分析
若定义 f(i,j) 为 (i, j)点到底边的最小路径和,则递归求解式为:
由此,将任一点到底边的最小路径和,转化为了与该点相邻两点到底边的最小路径和中的较小值,再加上该点本身的值。
得出(解一) 递归解法。
解一(递归)
1 2 3 4 5 6 7
| public int minimumTotal(List<List<Integer>> tri){ return dfs(tri,0,0); } private int dfs(List<List<Integer>> tri, int i, int j){ if (i==tri.size()) return 0; return Math.min(dfs(tri,i+1,j), dfs(tri,i+1, j+1)) + tri.get(i).get(j); }
|
缺点:暴力搜索会有大量重复计算,引出解法二,结合记忆化数组进行优化。
解法二:(递归+记忆化)
定义一个二位数组用来记忆化
1 2 3 4 5 6 7 8 9 10 11
| Integer[][] memo;
public int minimumTotal(List<List<Integer>> tri){ memo = new Integer[tri.size()][tri.size()]; return dfs(tri,0,0); } private int dfs(List<List<Integer>> tri, int i, int j){ if(i==tri.size()) return 0; if(memo[i][j]!=null) return memo[i][j]; return memo[i][j] = Math.min(dfs(tri, i+1, j), dfs(tri, i+1, j+1)) + tri.get(i).get(j); }
|
时间复杂度:$O(N^2)$,N为三角形的行数。
空间复杂度:$O(N^2)$,N为三角形的行数。
动态规划
定义二维 dp 数组,将解法二中「自顶向下的递归」改为「自底向上的递推」。
状态表示:$dp[i][j]$ 表示从点 $(i,j)$ 到底边的最小路径和
状态计算:$dp[i][j] = min(dp[i+1][j] , dp[i+1][j+1]]) + tri[i][j]$
1 2 3 4 5 6 7 8 9 10 11 12
| public int minmumTotal(List<List<Integer>> tri){ int n = tri.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]) + tri.get(i).get(j); } } return dp[0][0]; }
|
时间复杂度:$O(N^2)$,N为三角形的行数。
空间复杂度:$O(N^2)$,N为三角形的行数。
空间优化
上一个解定义了一个N行N列的 dp数组
但实际递推中发现,计算 $dp[i][j]$ 时,只用到了下一行的 $dp[i+1][j] 和 dp[i+1][j+1]$
因此dp 数组不需要定义N行,只需定义1行
把 $i$ 所在维度去掉,将就可以将 $O(N^2)$ 的空间复杂度优化成$ O(N)$
1 2 3 4 5 6 7 8 9 10
| public int minimumTotal(List<List<Integer>> tri) { int n = tri.size(); int[] dp = new int[n+1]; for(int i=n-1; j>=0; i--){ for(int j=0;j<=i; j++){ dp[j] = Math.min(dp[j], dp[j+1]) + tri.get(i).get(j); } } return dp[0]; }
|