返回

深入理解动态规划思想,轻松解题LeetCode 416:分割等和子集

后端

动态规划简介

动态规划是一种自底向上的优化算法,适用于求解具有最优子结构性质和重叠子问题的问题。其基本思想是将问题分解为较小的子问题,逐步解决这些子问题,最终得到整个问题的最优解。动态规划算法通常使用表格来存储子问题的最优解,以避免重复计算。

LeetCode 416:分割等和子集

题目

给定一个只包含正整数的非空数组 nums,判断是否可以将其划分为两个子集,使得这两个子集的和相等。

动态规划求解思路

对于该问题,我们可以使用动态规划来求解。首先,我们将问题分解为较小的子问题:对于数组 nums 的前 i 个元素,是否存在两个子集,使得它们的和相等。然后,我们使用表格 dp 来存储这些子问题的最优解。其中,dp[i][j] 表示对于数组 nums 的前 i 个元素,是否存在两个子集,使得它们的和分别为 j 和 nums[i] - j。

初始化时,我们令 dp[0][0] 为真,表示空数组可以被划分为两个空子集,它们的和都为 0。然后,对于 i 从 1 到 n 遍历,对于 j 从 0 到 nums[i] 遍历,我们可以使用以下公式来更新 dp[i][j]:

dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]]

该公式的含义是:对于数组 nums 的前 i 个元素,是否存在两个子集,使得它们的和分别为 j 和 nums[i] - j,取决于以下两种情况:

  1. 不考虑第 i 个元素,即使用数组 nums 的前 i - 1 个元素是否可以被划分为两个子集,它们的和分别为 j 和 nums[i] - j。
  2. 考虑第 i 个元素,即使用数组 nums 的前 i - 1 个元素是否可以被划分为两个子集,它们的和分别为 j - nums[i] 和 nums[i]。

如果以上两种情况中任意一种成立,则 dp[i][j] 为真,否则为假。

最终,我们只需要检查 dp[n][nums[i] / 2] 是否为真即可判断数组 nums 是否可以被划分为两个子集,使得它们的和相等。

代码实现

def canPartition(nums):
    """
    :type nums: List[int]
    :rtype: bool
    """
    # 计算数组 nums 的总和
    total = sum(nums)

    # 如果总和为奇数,则数组 nums 不能被划分为两个子集,使得它们的和相等
    if total % 2 == 1:
        return False

    # 初始化表格 dp
    dp = [[False] * (total // 2 + 1) for _ in range(len(nums) + 1)]

    # 初始化第一行和第一列
    for i in range(len(nums) + 1):
        dp[i][0] = True
    for j in range(1, total // 2 + 1):
        dp[0][j] = False

    # 填充表格 dp
    for i in range(1, len(nums) + 1):
        for j in range(1, total // 2 + 1):
            dp[i][j] = dp[i - 1][j]
            if j - nums[i - 1] >= 0:
                dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i - 1]]

    # 检查 dp[n][nums[i] / 2] 是否为真
    return dp[len(nums)][total // 2]

总结

动态规划是一种强大的算法范式,可用于解决各种优化问题。通过使用动态规划,我们可以将复杂的问题分解为较小的子问题,逐步解决这些子问题,最终得到整个问题的最优解。在本文中,我们介绍了动态规划的基本思想,并使用其解决了LeetCode 416:分割等和子集问题。通过详细的示例和代码,您对动态规划有了更深入的理解,并能够轻松解决此类问题。