三角形最小路径和

标签: 数组 动态规划

难度: Medium

给定一个三角形 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 为三角形的总行数)来解决这个问题吗?

Submission

运行时间: 20 ms

内存: 18.1 MB

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        n = len(triangle)
        dp = [[0 for _ in range(n)] for _ in range(n)]
        dp[0][0] = triangle[0][0]
        for i in range(1, n):
            for j in range(i+1):
                if i == j:
                    dp[i][j] = dp[i-1][j-1] + triangle[i][j]
                elif j == 0:
                    dp[i][j] = dp[i-1][j] + triangle[i][j]
                else:
                     dp[i][j] = min(dp[i-1][j], dp[i-1][j-1]) + triangle[i][j]
        return min(dp[n-1])

Explain

该题解使用动态规划的思路来解决问题。首先初始化一个二维 DP 数组,用于存储从三角形顶部到达每个位置的最小路径和。然后从第二行开始,逐行填充 DP 数组。对于每个位置,根据它的索引情况,选择上一行相邻位置中的较小值,加上当前位置的值,更新 DP 数组。最后,返回 DP 数组最后一行的最小值,即为从顶部到底部的最小路径和。

时间复杂度: O(n^2)

空间复杂度: O(n^2),可优化为 O(n)

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        n = len(triangle)
        # 初始化 DP 数组
        dp = [[0 for _ in range(n)] for _ in range(n)]
        # 初始化第一行的值
        dp[0][0] = triangle[0][0]
        # 从第二行开始遍历
        for i in range(1, n):
            for j in range(i+1):
                if i == j:
                    # 如果是最右边的元素,只能从上一行的最右边元素转移而来
                    dp[i][j] = dp[i-1][j-1] + triangle[i][j]
                elif j == 0:
                    # 如果是最左边的元素,只能从上一行的最左边元素转移而来
                    dp[i][j] = dp[i-1][j] + triangle[i][j]
                else:
                    # 如果在中间,从上一行相邻的两个元素中选择较小的
                    dp[i][j] = min(dp[i-1][j], dp[i-1][j-1]) + triangle[i][j]
        # 返回最后一行的最小值
        return min(dp[n-1])

Explore

在动态规划中,初始化 dp[i][j] 为 0 是出于在后续计算中只关注有效的三角形位置。在第一行初始化后,每个 dp[i][j] 都会根据上一行的值被适当更新。由于每个位置的最小路径和是由上一行的一个或两个位置决定的,只要保证这些位置正确初始化,再逐行更新,就不会影响最终的计算结果。初始化为 0 是合理的,因为只有被更新的 dp[i][j] 才会被用来计算路径和,未被更新的值(即始终为0的值)不影响最终结果,因为它们不属于有效的路径计算范围。

在处理边界元素时,它们的状态转移方程略有不同,因为边界元素的相邻元素数量不同于中间元素。对于最左边的元素(即 j == 0 的元素),它只能从其正上方的元素(即上一行的 dp[i-1][0])转移过来,因为左边没有其他元素。对于最右边的元素(即 j == i 的元素),它只能从上一行的最右边的元素(即 dp[i-1][j-1])转移过来,因为右边没有其他元素。而中间的元素可以从正上方和左上方两个元素中选择一个较小的值来转移,这是因为它们位于两个元素之间,可以从两个方向获得可能的最小路径和。

最终返回 dp[n-1] 的最小值而不是 dp[n-1][0] 或 dp[n-1][n-1] 是因为三角形的底边可能有多个元素,并且每个元素都可能是不同路径的终点。由于我们求的是从顶部到底部的最小路径和,因此需要考虑到从顶部到三角形底部的所有可能路径,并从中选择一个全局的最小值。dp[n-1] 表示三角形最后一行的所有元素,每个元素代表到达该位置的最小路径和;因此,我们需要从这些值中找到最小的一个,这就是为什么要返回整个行的最小值而不是固定位置的值。