前端算法第一八九弹-三角形最小路径和

129 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情

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

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

动态规划

我们用 f[i][j]f[i][j] 表示从三角形顶部走到位置 (i,j)(i,j) 的最小路径和。这里的位置 (i,j)(i,j) 指的是三角形中第 ii 行第 jj 列(均从 0 开始编号)的位置。

由于每一步只能移动到下一行「相邻的节点」上,因此要想走到位置 (i,j)(i,j),上一步就只能在位置 (i1,j1)(i−1,j−1) 或者位置 (i1,j)(i−1,j)。我们在这两个位置中选择一个路径和较小的来进行转移,状态转移方程为:

f[i][j]=min(f[i1][j1],f[i1][j])+c[i][j]f[i][j]=min(f[i−1][j−1],f[i−1][j])+c[i][j]

其中 c[i][j]c[i][j] 表示位置 (i,j)(i,j) 对应的元素值。

注意第 ii 行有 i+1i+1 个元素,它们对应的 jj 的范围为 [0,i][0,i]。当 j=0j=0j=ij=i 时,上述状态转移方程中有一些项是没有意义的。例如当 j=0j=0 时,f[i1][j1]f[i−1][j−1] 没有意义,因此状态转移方程为:

f[i][0]=f[i1][0]+c[i][0]f[i][0]=f[i−1][0]+c[i][0]

即当我们在第 ii 行的最左侧时,我们只能从第 i1i−1 行的最左侧移动过来。当 j=ij=i 时,f[i1][j]f[i−1][j] 没有意义,因此状态转移方程为:

f[i][i]=f[i1][i1]+c[i][i]f[i][i]=f[i−1][i−1]+c[i][i]

即当我们在第 ii 行的最右侧时,我们只能从第 i1i−1 行的最右侧移动过来。

最终的答案即为 f[n1][0]f[n−1][0]f[n1][n1]f[n−1][n−1] 中的最小值,其中 nn 是三角形的行数。

const minimumTotal = (triangle) => {
    const h = triangle.length;
    const dp = new Array(h);// 初始化状态数组
    for (let i = 0; i < h; i++) {
        dp[i] = new Array(triangle[i].length);
    }

    for (let i = h - 1; i >= 0; i--) { //从三角形底向上上遍历
        for (let j = 0; j < triangle[i].length; j++) { //遍历同层
            if (i == h - 1) {  //基本情况 三角形最下一层的状态
                dp[i][j] = triangle[i][j];
            } else { // 状态转移方程,从下一层相邻位置选取一个较小者 + 自身
                dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j];
            }
        }
    }
    return dp[0][0];
};

//状态压缩
const minimumTotal = (triangle) => {
    const bottom = triangle[triangle.length - 1];
    const dp = new Array(bottom.length);
    for (let i = 0; i < dp.length; i++) {//最后一行
        dp[i] = bottom[i];
    }
    for (let i = dp.length - 2; i >= 0; i--) {// 从倒数第二列开始迭代
        for (let j = 0; j < triangle[i].length; j++) {
            dp[j] = Math.min(dp[j], dp[j + 1]) + triangle[i][j];
        }
    }
    return dp[0];
};