设计题题目:剑指offer T100 三角形中最小路径之和
题目:在一个由数字组成的三角形中,第1行有1个数字,第2行有2个数字,以此类推,第n行有n个数字。
例如,右图是一个包含4行数字的三角形。如果每步只能前往下一行中相邻的数字,请计算从三角形顶部到底部的路径经过的数字之和的最小值。如右图所示,从三角形顶部到底部的路径数字之和的最小值为11,对应的路径经过的数字用阴影表示。
暴力法来分析:
第一条路径:2-3-6-4
第二条路径:2-3-6-1
第三条路径:2-3-5-1
…
第n条路径:2-4-7-3
n = 2$\times 2 2 2\times$2 = 8条
时间复杂度 = O(2h) = O(n)
那有没有稍微复杂度降低点的算法呢?
动态规划法
从顶点到结点某一结点的每一条最小路径中,顶点到上一层结点也必定是最小路径。
如从顶点“2”到结点“1”的最小路径“2-3-5-1”中,2到5也必定是最小路径“2-3-5”而非“2-4-5”,这样才能保证最后得到的答案是最小路径。
因此到某结点的最小路径值等于本节点的距离+上一层的最小路径之和。
由上我们不难得出该题动态规划的状态递推关系:
minDistance[i][j] = min(minDistance[i][j-1], minDistance[i-1][j-1])+nums[i][j]
动态规划时间复杂度分析
首先纪录第2层的min值
min[1][0] = 3+2 = 5
min[1][1] = 4+2 = 6
第3层:
min[2][0] = 6 + 5 = 11
min[2][1] = 5 + min{5, 6} = 10
min[3][1] = 7 + 6 =13
第4层:
…
min[3][1] = 1 + min{11, 10} = 11;
…
最后比较min中最后一层的值,得到最小的路径之和;
由上我们不难看出该算法的复杂度为O(n)
质的飞越啊!!!
代码实现
import java.util.*;
class Solution {
public ArrayList<Integer> list;
public int height;
public int[][] nums; //保存数字
public int[][] min; //保存最小路径值
public int[][] lastRou; //保存最小路径
Solution(List list){
this.list = new ArrayList<>(list);
this.height = getListHigh();
}
//通过list初始化各数组
public void initArr(){
nums = new int[height][height]; //保存数字
min = new int[height][height]; //保存最小路径值
lastRou = new int[height][height]; //保存最小路径
int index = 0;
for (int i = 0; i < height; i++) {
if (i == 0) {
//在第一层只需要赋值,无需比较上层
min[0][0] = list.get(index++);
nums[0][0] = min[0][0];
continue;
}
for (int j = 0; j < i + 1; j++) {
nums[i][j] = list.get(index++);
int leftNum = j == 0? Integer.MAX_VALUE : nums[i][j]+min[i-1][j-1];
//当j==0直接赋值为右边值加一,则必定会比右边的值大
int rightNum = j == i? Integer.MAX_VALUE : nums[i][j]+min[i-1][j];
if (leftNum <= rightNum){
min[i][j] = leftNum;
lastRou[i][j] = j-1;
}else {
min[i][j] = rightNum;
lastRou[i][j] = j;
}
}
}
}
//获取最小路径之和
public int[] getMinLen(){
//获取最小路径值
int minLen = Integer.MAX_VALUE;
int lastLevel = 0;
for (int i = 0; i < height; i++) {
if (min[height-1][i] < minLen) {
minLen = min[height-1][i];
lastLevel = i;
}
}
int arr[] = {minLen, lastLevel};
return arr;//返回最小路径的最后一个值
}
//获取最小路径
public int[] getMinRou(int lastLevel){
//lastlevel为最小路径的最后一个节点
int[] realRou = new int[height];
for (int i = height-1; i >= 0; i--) {
realRou[i] = nums[i][lastLevel];
int rot = lastRou[i][lastLevel];
lastLevel = rot;
}
return realRou;
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
Collections.addAll(list, 2,3,4,6,5,7,4,1,8,3,1,2,3,4,5);
Solution s = new Solution(list);
s.showList(); //显示列表的树状图
s.initArr(); //初始化s中的各个数组
int result[] = s.getMinLen();//result[0]为最小路径值,result[1]为最小路径的最后一层结点
System.out.println("最小路径值为: " + result[0]);
int []realRou = s.getMinRou(result[1]);
System.out.print("最小路径依次为: ");
for (int i = 0; i < realRou.length; i++) {
System.out.print(realRou[i]);
}
}
public void showList(){
System.out.println("该列表的树状图如下: ");
boolean flag = true;
int index = 0;
for(int i=1;i<=this.height;i++){
for(int j=this.height; i<j; j--)
System.out.print(" ");
for(int j=1; j<=i; j++)
if (flag == true){
flag = false;
System.out.print(this.list.get(index++));
}
else {
flag = true;
System.out.print(" ");
}
for(int j=1; j<i; j++)
if (flag == true){
flag = false;
System.out.print(this.list.get(index++));
}
else {
flag = true;
System.out.print(" ");
}
flag = true;
System.out.println();
}
System.out.println("该列表的高度为 :" + this.height);
}
public int getListHigh(){
int len = this.list.size();
int high = 1;
int account = 0;
while (account < len){
account += high++;
}
high--;
return high;
}
}