算法题分享 | 三角形最小路径和

题目

给定一个三角形 triangle ,找出自顶向下的最小路径和。

每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 ii + 1

示例 1:

输入: triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出: 11
解释: 如下面简图所示:
   2
  3 4
 6 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

示例 2:

输入: triangle = [[-10]]
输出: -10

提示:

  • 1 <= triangle.length <= 200
  • triangle[0].length == 1
  • triangle[i].length == triangle[i - 1].length + 1
  • -10⁴ <= triangle[i][j] <= 10⁴

进阶:

  • 你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题吗?

题解

解题思路

我们可以进行问题拆解,将问题拆解为更小的问题,利用更小问题的最优解来得到原问题的解。要计算到达某行的最小路径和,可以先算出到达前一行各个结点的最小路径和,然后基于这一结果可以算出到达本行各个结点的最小路径和,最终取其中的最小值,这也就是动态规划算法。所以我们只需要从第 1 行开始,通过该方法依次算出到达第 2、3、4… 行各节点最小路径和,算完最后一行即可取得答案。这样我们就把为原问题拆解为了多个小规模的子问题,并且子问题间是存在重叠,需要依赖其他子问题的计算结果,这样也可以减少重复的计算。接下来我们需要先捋一下动态规划的状态转移方程。

状态转移方程
由前面的解释可以得到状态转移方程:

f(i,j) = min(f(i-1,j), f(i-1,j-1)) + triangle[i][j]

其中,f(i,j) 表示移动到第 i 行第 j 个结点的最小路径和,i、j 均从 0 开始算;triangle[i][j] 表示三角形中的节点。

初始值
方程的初始值即为 f(0,0) = triangle[0][0]

其他边界值
在转移方程中,当 j = 0,或者 i = j 时部分值是没有意义的,
当 j = 0,简化为:f(i,j) = f(i-1,j) + triangle[i][j];
当 i = j,简化为:f(i,j) = f(i-1,j-1) + triangle[i][j]。

代码

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int size = triangle.size();
        int[][] dp = new int[size][size];
        dp[0][0] = triangle.get(0).get(0);


        for (int i = 1; i < size; i++) {
            
            dp[i][0] = dp[i - 1][0] + triangle.get(i).get(0);
            
            for (int j = 1; j < i; j++) {
                // 公式
                // f(i,j) = min(f(i-1,j), f(i-1,j-1)) + triangle.get(i).get(j);
                dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - 1]) + triangle.get(i).get(j);
            }

            dp[i][i] = dp[i - 1][i - 1] + triangle.get(i).get(i);
        }

        int res = dp[size - 1][0];
        for (int i = 1; i < size; i++) {
            res = Math.min(res, dp[size - 1][i]);
        }

        return res;

    }
}

复杂度分析

  • 时间复杂度:O(n²)
    其中 n 是三角形的行数
  • 空间复杂度:O(n²)
    使用了一个二维数组 dp。

进阶-空间优化
考虑如何把空间复杂度缩小到 O(n)?

实际上 dp 定义为一个长度为 n 的一维数组即可满足使用,要改变的是对于每行遍历列时需要从右到左进行遍历,为什么要这样呢?
因为如果是从左到右的话,当获取当前行的 dp[j] 的时候,就会把上一行保留的 dp[j] 结果给覆盖点,这样
就没法算 dp[j + 1] 了;
但是若从右到左,计算过程中给 dp 赋值的位置都是后面待计算结点不用依赖的,不会发生冲突。

优化后代码

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int size = triangle.size();
        int[] dp = new int[size];
        dp[0] = triangle.get(0).get(0);


        for (int i = 1; i < size; i++) {
            
            dp[i] = dp[i - 1] + triangle.get(i).get(i);
            
            for (int j = i - 1; j > 0; j--) {
                // 公式
                // f(i,j) = min(f(i-1,j), f(i-1,j-1)) + triangle.get(i).get(j);
                dp[j] = Math.min(dp[j], dp[j - 1]) + triangle.get(i).get(j);
            }

            dp[0] = dp[0] + triangle.get(i).get(0);

        }

        int res = dp[0];
        for (int i = 1; i < size; i++) {
            res = Math.min(res, dp[i]);
        }

        return res;

    }
}

复杂度分析

  • 时间复杂度:O(n²)
    其中 n 是三角形的行数
  • 空间复杂度:O(n)
    只使用了一个一维数组 dp。

你可能感兴趣的:(算法,数据结构,算法,动态规划)