坚持每天一道算法题目…
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
像之前的题目一样分为三个步骤。1、递归解题 在理解过程之后 尝试找出 动态关系方程 2、使用动态关系方程解题 3、使用动态关系方程自底向上解题。
思考这道题的时候,我首先想到的办法当然就是递归了,利用递归的方式 找出所有的路径,然后将所有的路径存储在一个集合当中,最后将结果排序。显然这不是最优的解题方法,但是为了帮助自己理解问题 ,还是先进行了代码书写。
@Test
public void test01() {
int[][] arr = {
{2},
{3, 4},
{6, 5, 7},
{4, 1, 8, 3},
};
List<Integer> target = new ArrayList<>();
// 调用递归函数
genTr(target, 0, 0, arr, 0);
// 进行排序
target.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
System.out.println(target);//取出list[0] 就是最小的一个。递归无法解决 超时
}
//获得路径之和 使用递归的方法
void genTr(List<Integer> target, int i, int j, int[][] arr, int pathAndCur) {
// 递归出口 如果超出边界 那么就直接返回,
if (i >= arr.length || j >= arr[j].length) {
return;
}
//到达最后一层 就可以把数据存储起来
if (i == arr.length - 1) {
target.add(arr[i][j] + pathAndCur);
return;
}
// 递归时 把当前节点的路径和传入 进行下一轮递归
//当前点的左边递归
genTr(target, i + 1, j, arr, pathAndCur + arr[i][j]);
//当前点的右边递归
genTr(target, i + 1, j + 1, arr, pathAndCur + arr[i][j]);
}
用这个方法,能够正确输出答案, 但是当给出的用例三角组合特别大的时候,将会进行大量递归。时间复杂度会很高。当这代码提交时,也就没有通过,显示超时。
定义DP(i,j)为 下一层到当前节点的最小路径。
那么可以推导出 DP(i,j)= min(DP(i+1,j),DP(i+1,j+1)) + arr[i][j] 其实可以把这个三角形关系 理解成 父节点与孩子节点的关系。到父节点的最小值,也就是左后孩子节点当中路径最小值 加上 当前的父亲节点。有了这个关系式。我就再进一步使用了递归的方式解题。
@Test
public void test02() {
int[][] arr = {
{2},
{3, 4},
{6, 5, 7},
{4, 1, 8, 3},
};
System.out.println(dp(arr, 0, 0));
}
int dp(int[][] arr, int i, int j) {
if (i >= arr.length || j >= arr[j].length) {
return 0;
}
int left = dp(arr, i + 1, j);
int right = dp(arr, i + 1, j + 1);
return left + arr[i][j] < right + arr[i][j] ? left + arr[i][j] : right + arr[i][j];
}
当提交代码之后,结果还是如出一辙,时间太久 超时。看来还是不能用递归的方式求解。那么就模仿斐波拉契数列一样,自底向上的方式,使用动态推导 表达式进行求解。
//使用自底向上的方法dp[i,j] = min(dp(i+1,j),dp(i+1,j+1))+arr[i,j] 求解
@Test
public void test04() {
List<List<Integer>> list = new ArrayList<>();
list.add(Arrays.asList(2));
list.add(Arrays.asList(3, 4));
list.add(Arrays.asList(6, 5, 7));
list.add(Arrays.asList(4, 1, 8, 3));
//从倒数第二行开始 因为第一行的值已经确定了
int[] dp = new int[ list.get(list.size()-1).size()+1];
//使用双层遍历 自底向上的求解
for (int i = list.size() - 1; i >= 0; i--) {
for (int j = 0; j < list.get(i).size(); j++) {
//取出比较小的值,然后存储起来。复用同一个数组 dp
dp[j] = list.get(i).get(j)+dp[j]<list.get(i).get(j)+dp[j+1]?list.get(i).get(j)+dp[j]:list.get(i).get(j)+dp[j+1];
}
}
System.out.println(dp[0]);
}