返回

LeetCode 120:自顶向下动态规划求三角形最小路径和

前端

从三角形顶点到底部的最小路径和

简介

在三角形中穿行,从顶点到底部,每一步只能移动到相邻的结点,如何在每一步都做出正确的选择,以找到从顶点到底部最省力的路径?这个问题看似简单,却蕴含着数学和计算机科学中的奥秘。

动态规划解法

动态规划是一种自底向上的问题求解方法,我们将问题分解成较小的子问题,逐步解决并存储子问题的解,避免重复计算。对于三角形最小路径和问题,我们定义dp[i][j]表示从顶点到第i行第j列元素的最小路径和。从底向上逐行计算,利用子问题的解来推导出当前行的解。

代码示例:

def min_path_triangle(triangle):
    """
    返回三角形从顶点到底部的最小路径和。
    """
    # 初始化动态规划表
    n = len(triangle)
    dp = [[0] * i for i in range(1, n + 1)]

    # 从底向上计算动态规划表
    for i in range(n - 1, -1, -1):
        for j in range(i + 1):
            # 计算当前元素的最小路径和
            dp[i][j] = min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j]

    # 返回顶点的最小路径和
    return dp[0][0]

深度优先搜索解法

深度优先搜索是一种递归算法,它沿着一条路径深度探索,直到找到解或遍历完所有可能性。对于三角形最小路径和问题,我们可以使用深度优先搜索从顶点开始探索,沿着每条可能的路径进行递归,直到到达底部,并返回最小的路径和。

代码示例:

def min_path_dfs(triangle):
    """
    使用深度优先搜索查找三角形从顶点到底部的最小路径和。
    """

    def dfs(i, j):
        # 达到底部,返回当前元素的路径和
        if i == len(triangle) - 1:
            return triangle[i][j]

        # 沿着左子树和右子树递归探索
        left = dfs(i + 1, j)
        right = dfs(i + 1, j + 1)

        # 返回较小的路径和
        return min(left, right) + triangle[i][j]

    # 从顶点开始深度优先搜索
    return dfs(0, 0)

递归解法

递归是一种将问题分解成相同或相似的子问题的算法。对于三角形最小路径和问题,我们可以使用递归从顶点开始,沿着每条可能的路径进行递归,直到到达底部,并返回最小的路径和。

代码示例:

def min_path_recursive(triangle):
    """
    使用递归查找三角形从顶点到底部的最小路径和。
    """

    def dfs(i, j):
        # 达到底部,返回当前元素的路径和
        if i == len(triangle) - 1:
            return triangle[i][j]

        # 沿着左子树和右子树递归探索
        left = dfs(i + 1, j)
        right = dfs(i + 1, j + 1)

        # 返回较小的路径和
        return min(left, right) + triangle[i][j]

    # 从顶点开始递归
    return dfs(0, 0)

常见问题解答

  1. 为什么使用动态规划来解决这个问题?
    动态规划是一种高效的算法,它可以避免重复计算,从而节省时间和空间复杂度。

  2. 深度优先搜索和递归算法之间有什么区别?
    深度优先搜索沿着一条路径深度探索,而递归将问题分解成相同或相似的子问题。

  3. 为什么在三角形的底部行初始化动态规划表?
    因为底部的最小路径和就是每个元素本身,这是动态规划算法的基础。

  4. 为什么在深度优先搜索中计算左子树和右子树的最小路径和?
    因为从当前元素只能移动到下一行中的相邻结点,因此我们需要考虑这两个路径。

  5. 为什么递归算法的时间复杂度为指数级?
    因为递归算法会沿着每条可能的路径递归探索,因此随着三角形高度的增加,时间复杂度呈指数增长。