力扣(120)--------三角形最小路径和

坚持每天一道算法题目…

题目描述:

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

分析过程:

像之前的题目一样分为三个步骤。1、递归解题 在理解过程之后 尝试找出 动态关系方程 2、使用动态关系方程解题 3、使用动态关系方程自底向上解题。

1. 递归

思考这道题的时候,我首先想到的办法当然就是递归了,利用递归的方式 找出所有的路径,然后将所有的路径存储在一个集合当中,最后将结果排序。显然这不是最优的解题方法,但是为了帮助自己理解问题 ,还是先进行了代码书写。

  @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]);
    }

你可能感兴趣的:(算法)