题目描述

给定一个三角形 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
  • -104 <= triangle[i][j] <= 104

进阶:

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

解题思路

解题标签:动态规划 记忆化搜索 递归

解决这道题其实可以借鉴递归版本的斐波那契数列代码,使用递归函数,我们只需要关注当前走到哪一步,下面的步骤交给递归函数来解决,当然得注意临界条件。

每次递归调用时分别计算当前节点(i, j)下一层(i+1, j)(i+1, j+1)两个节点的最短路径,最后比较一下,返回最小值,同时存入容器。

此外,题目规定要使用O(n)的时间复杂度,可以通过记忆化搜索来解决。所谓记忆化搜索就是为防止重复计算,把每次计算好的结果存到某个容器中,这样如果再遇到需要重复计算的时候直接从容器中拿结果,时间更快。

image-20210430175913938

例如当走到3这个节点时,需要递归计算下一层6和5,当走到4这个节点时,需要计算下一层5和7,这样一来5节点就重复计算了两次。因此,如果把计算好的结果存入某个容器中,再用到的时候直接取出来可以极大地提高运行速度。而且递归调用需要频繁的出栈入栈,也非常耗时间,使用记忆化搜索可以有效提升运行速度。

AC代码

public class T120 {

    // 存储计算结果
    private Map<String, Integer> results = new HashMap<>();

    public int minimumTotal(List<List<Integer>> triangle) {
        return cal(triangle, 0, 0);
    }

    private int cal(List<List<Integer>> triangle, int index, int column) {

        // 临界条件
        if (triangle.size() <= index) {
            return 0;
        }

        int currentValue = triangle.get(index).get(column);

        if (index == (triangle.size() - 1)) {
            return currentValue;
        }

        int a = 0;
        int b = 0;

        // 判断是否已经计算过
        if (!results.containsKey((index + 1) + ":" + column)) {
            // 递归调用(index+1, column)
            a = currentValue + cal(triangle, index + 1, column);
        } else {
            a = currentValue + results.get((index + 1) + ":" + column);
        }
        // 判断是否已经计算过
        if (!results.containsKey((index + 1) + ":" + (column+1))) {
            // 递归调用(index+1, column)
            b = currentValue + cal(triangle, index + 1, column + 1);
        } else {
            b = currentValue + results.get((index + 1) + ":" + (column+1));
        }
        // 比较,取最小值
        if (a <= b) {
            // 存入容器
            results.put(index + ":" + column, a);
            return a;
        } else {
            results.put(index + ":" + column, b);
            return b;
        }
    }

    @Test
    public void test27() {

        List<Integer> l1 = Arrays.asList(2);
        List<Integer> l2 = Arrays.asList(3, 4);
        List<Integer> l3 = Arrays.asList(6, 5, 7);
        List<Integer> l4 = Arrays.asList(4, 1, 8, 3);
        List<List<Integer>> input = new ArrayList<>();
        input.add(l1);
        input.add(l2);
        input.add(l3);
        input.add(l4);
        int ret = minimumTotal(input);
        System.out.println(ret);
    }
}

总结

第一次接触动态规划相关题目说实话卡了快一天,不过最后还是AC了!

image-20210430170851222

官方其实有给示例代码,但是笔者比较菜,看的不是太明白,就按自己的思路解决的。

革命尚未成功,仍需努力!