动态规划为什么要用 额外的一个格子呢?

本文由 ChatGPT 生成,方便复习用。

观察到有一些题目,棋盘明明是 m*n,做 dp 的时候,却要(m+1) * (n+1) 个空间,于是总结几道题如下。

动态规划问题中经常会用到 n+1k+1dp 数组,以便更方便地处理边界情况。

我将给你几个例子,以加深理解:

1. 零钱兑换问题:

问题描述:给定不同面额的硬币 coins 和一个总金额 amount,计算出可以凑成总金额所需的最少硬币数量。如果无法凑成,返回 -1。

在这个问题中,我们可以使用 dp 数组来记录每个金额对应的最少硬币数量。创建 dp 数组时,通常会多加一项,例如 dp[amount + 1],以处理金额为 0 的情况。

def coinChange(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0

    for coin in coins:
        for i in range(coin, amount + 1):
            dp[i] = min(dp[i], dp[i - coin] + 1)

    return dp[amount] if dp[amount] != float('inf') else -1

2. 最长递增子序列问题:

问题描述:给定一个无序的整数数组,找到其中最长上升子序列的长度。

在这个问题中,我们通常使用 dp 数组来记录以每个元素结尾的最长上升子序列的长度。创建 dp 数组时,也会多加一项,例如 dp[n],以处理空序列的情况。

def lengthOfLIS(nums):
    n = len(nums)
    dp = [1] * n

    for i in range(n):
        for j in range(i):
            if nums[i] > nums[j]:
                dp[i] = max(dp[i], dp[j] + 1)

    return max(dp) if n > 0 else 0

3. 走棋盘问题:

问题描述:在一个 m x n 的棋盘上,从左上角走到右下角,每次只能向右或向下移动一步,求所有可能的路径数量。

在这个问题中,我们可以使用 dp 数组来记录从起点到每个格子的路径数量。创建 dp 数组时,通常会多加一行和一列,例如 dp[m+1][n+1],以处理起点和终点的情况。

def uniquePaths(m, n):
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    dp[1][1] = 1

    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if i > 1:
                dp[i][j] += dp[i - 1][j]
            if j > 1:
                dp[i][j] += dp[i][j - 1]

    return dp[m][n]

4. 分割等和子集问题:

问题描述:给定一个只包含正整数的非空数组 nums,判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

在这个问题中,我们可以使用 dp 数组来记录能否从前 i 个元素中选择若干个元素,使得它们的和等于 j。创建 dp 数组时,通常会多加一行和一列,例如 dp[n+1][target+1],以处理空数组和空目标的情况。

def canPartition(nums):
    total_sum = sum(nums)
    if total_sum % 2 != 0:
        return False

    target = total_sum // 2
    n = len(nums)

    dp = [[False] * (target + 1) for _ in range(n + 1)]
    dp[0][0] = True

    for i in range(1, n + 1):
        for j in range(target + 1):
            if j >= nums[i - 1]:
                dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i - 1]]
            else:
                dp[i][j] = dp[i - 1][j]

    return dp[n][target]

5. 房屋偷盗问题(原问题):

问题描述:小偷想要从一排房屋中偷取现金,但不能偷相邻的房屋。每个房屋中有一定数量的现金,小偷的目标是偷取到最多的现金,但不能触发警报。

在这个问题中,我们使用了 (n + 1) x (k + 1)dp 数组,以处理偷取 0 间房屋和偷取至少 k 间房屋的情况。

def minStealing(nums, k):
    n = len(nums)
    dp = [[float('inf')] * (k + 1) for _ in range(n + 1)]
    dp[0][0] = 0

    for i in range(1, n + 1):
        for j in range(1, k + 1):
            dp[i][j] = min(dp[i - 1][j], dp[i - 2][j - 1] + nums[i - 1])

    return dp[n][k]

希望这些例子有助于更好地理解如何使用 n+1k+1dp 数组来处理不同类型的动态规划问题。

要注意的是:

dp = [[0] * (n + 1) for _ in range(m + 1)] 这种构建,要用 for。

在构建二维数组时,使用 * (m + 1) 的方式可以创建 m+1 个相同的列表对象的引用,这意味着所有子列表都指向同一个列表对象。这样的话,如果你修改其中一个子列表的值,所有子列表都会发生变化,这通常不是我们想要的行为。

为了避免这种问题,通常建议使用列表推导式或循环来创建二维数组,确保每个子列表都是独立的对象,不会相互影响。例如,使用 dp = [[0] * (n + 1) for _ in range(m + 1)] 就可以确保每个子列表都是独立的,不会相互影响。


了解 小匚的个人博客 的更多信息

订阅后即可通过电子邮件收到最新文章。

发表评论

了解 小匚的个人博客 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读

了解 小匚的个人博客 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读