[编程题] lk [120. 三角形最小路径和(动态规划)
[编程题] lk 120. 三角形最小路径和(动态规划)
在本文中会比较贪心算法和动态规划解题的场合
题目
输入输出
思路
贪心算法(本题不适用)
-
首先我们会想到
贪心算法
贪心算法是什么呢?它其实是一种只会考虑当步最优,每一步,都保证在本次选择到的是最优的一个选择。但是它无法保证在本题中的全局最优,所以其实是用贪心无法很好的解决本题。
关于贪心解决办法存在的问题图解如下:
解释:
在如上的案例中,我们如果是按照贪心算法每次找局部最优,那么走完到最后一行的时候是被迫选择比较大的值,得出的结果也是大的。
但是,如果不使用贪心算法,使用动态规划的话,就会保存同样一个这样的三角形的dp二维数组,那么,我们在最后一行中,选出一个最小的值就是我们要的那个最短路径。
我们用贪心算法解决此问题代码如下:
//方法1 贪心思想(存在错误,是局部最优而不是全局最优)
public int minimumTotal(List<List<Integer>> triangle) {
//我们用dp表示从顶层走到本层的最大的路径和的值
int[][] dp = new int[triangle.size()][2]; //使用二维表示
dp[0][0] = triangle.get(0).get(0); //存储首个元素的值
dp[0][1] = 0; //第二维度存储此值的数组索引
for(int i=1;i<triangle.size();i++){
//根据上次的索引情况,确定本次需要走的本行的索引
int curIndex = triangle.get(i).get(dp[i-1][1])<triangle.get(i).get(dp[i-1][1]+1)?dp[i-1][1]:dp[i-1][1]+1;
//本行的累加的最小值
dp[i][0] = dp[i-1][0]+Math.min(triangle.get(i).get(dp[i-1][1]),triangle.get(i).get(dp[i-1][1]+1));
//记录本次使用的索引
dp[i][1] = curIndex;
}
//返回最后一行中二维数组中的数据域,而不是索引域
return dp[triangle.size()-1][0];
}
输出:
我们会发现答案错误的原因是因为每次只是选择了局部最优解。
思考
如果我们考虑贪心思想,只考虑当前层到下一层的时候选哪个数最合适的话,但是这种情况看不到三角形全貌,有可能在最下边的一层出现两个很大的数,此路径和还是很大的。所以,我们不能采取局部最优的贪心,要采用递归或者动态规划
动态算法(本题适用)
思路
-
首先,我们创建一个n*n的二维数组 dp,用来保存到达某个节点的时候目前的一个最优值情况。
-
在dp方程中,比如处理第2行,那么第二行中的第一个元素中的值只能是来自上一行dp[i-1]的第0列(因为每次从上往下走只能是下和右方向)
-
在dp方程中,比如处理第2行,那么第二行中的最后一个元素中的值只能是来自上一行dp[i-1]的第最后一个的元素(因为每次从上往下走只能是下和右方向)
上述两行表达的意思如下图:
-
我们依照这种思路构造好dp的值,那么我们需要的最短路径就是在dp的最后一行中的最小值。这是一种考虑到全局的最优的一个值。
代码
//动态规划
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++){
int m = triangle.get(i).size();
/*这里j只取从1开始,m-2结束是因为每行的第一个元素只可能是从上边元素一行的第一个元素来的,再加上自己的本身,每行的
最后一个元素只能是来自上一行的最后一个元素,再加上自己本身*/
for(int j=1;j<m-1;j++){
dp[i][j] = Math.min(dp[i-1][j-1],dp[i-1][j])+triangle.get(i).get(j);
}
//处理内层for中j=0的值的情况,原因见上述
dp[i][0] = dp[i-1][0] + triangle.get(i).get(0);
//处理内存for中j=m-1的情况,原因见上
dp[i][m-1] = dp[i-1][m-2] + triangle.get(i).get(m-1);
}
//退出for循环后dp的三角形就存储好了值信息,我们从最后一行找出一个最小的值即最短路径
int res = dp[n-1][0];
for(int i=1;i<n;i++){
if(dp[n-1][i]<res){
res = dp[n-1][i];
}
}
return res;
}
输出: