2024.08.02 代码随想录 | 动态规划 背包问题

343. 整数拆分*

给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积 。

最总要的就是想明白递推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j})

随想录解法:

class Solution:
         # 假设对正整数 i 拆分出的第一个正整数是 j(1 <= j < i),则有以下两种方案:
        # 1) 将 i 拆分成 j 和 i−j 的和,且 i−j 不再拆分成多个正整数,此时的乘积是 j * (i-j)
        # 2) 将 i 拆分成 j 和 i−j 的和,且 i−j 继续拆分成多个正整数,此时的乘积是 j * dp[i-j]
    def integerBreak(self, n):
        dp = [0] * (n + 1)   # 创建一个大小为n+1的数组来存储计算结果
        dp[2] = 1  # 初始化dp[2]为1,因为当n=2时,只有一个切割方式1+1=2,乘积为1
       
        # 从3开始计算,直到n
        for i in range(3, n + 1):
            # 遍历所有可能的切割点
            for j in range(1, i // 2 + 1):

                # 计算切割点j和剩余部分(i-j)的乘积,并与之前的结果进行比较取较大值
                
                dp[i] = max(dp[i], (i - j) * j, dp[i - j] * j)
        
        return dp[n]  # 返回最终的计算结果

96.不同的二叉搜索树*

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

难点也是递推公式:dp[i] += dp[j - 1] * dp[i - j]; ,j-1 为j为头结点左子树节点数量,i-j 为以j为头结点右子树节点数量

class Solution:
    def numTrees(self, n: int) -> int:
        dp = [0] * (n + 1)
        dp[0] = 1
        dp[1] = 1
        for i in range(2, n+1):
            for j in range(i):
                dp[i] += dp[i-j-1] * dp[j]
        return dp[n]

 随想录解法:

class Solution:
    def numTrees(self, n: int) -> int:
        dp = [0] * (n + 1)  # 创建一个长度为n+1的数组,初始化为0
        dp[0] = 1  # 当n为0时,只有一种情况,即空树,所以dp[0] = 1
        for i in range(1, n + 1):  # 遍历从1到n的每个数字
            for j in range(1, i + 1):  # 对于每个数字i,计算以i为根节点的二叉搜索树的数量
                dp[i] += dp[j - 1] * dp[i - j]  # 利用动态规划的思想,累加左子树和右子树的组合数量
        return dp[n]  # 返回以1到n为节点的二叉搜索树的总数量

背包问题

01 背包

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

dp[i][j]表示的是从0-i物品中任取放入承重为j的背包里面,每个物品最多取一件,最多能装多大价值的物品。

02 背包 

def test_2_wei_bag_problem1():
    # 2 2 3 1 5 2
    # 2 3 1 5 4 3
    weight = [2, 2, 3, 1, 5, 2]
    value = [2, 3, 1, 5, 4, 3]
    bagweight = 3

    # 二维数组
    dp = [[0] * (bagweight + 1) for _ in range(len(weight))]

    # 初始化 因为dp[i][j]要用前一行的值,所以第0行需要我们进行初始化,第0行就是最多可以取一件物品0
    for j in range(weight[0], bagweight + 1):
        dp[0][j] = value[0]

    # weight数组的大小就是物品个数
    for i in range(1, len(weight)):  # 遍历物品
        for j in range(bagweight + 1):
            if j < weight[i]:
                # 物品i放不下背包时
                dp[i][j] = dp[i - 1][j]
            else:
                # 物品i能放得进背包时,看是不放i更大还是放i更大
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
    print(dp)

    # 一维数组
    dp = [0] * (bagweight + 1)
    for i in range(len(weight)):
        for j in range(bagweight, weight[i]-1, -1):
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
        print(dp)

416. 分割等和子集

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

解法:化为01背包问题

此时bagweight = sum(nums) // 2

物品i的重量和价值都是nums[i],其实这里不需要考虑物品价值,

dp[i][j]表示的是从0-i物品中任取,每个物品最多取一件,能否达到质量j

dp[j] = dp[j] or dp[j - num] 表示两种情况:

不放质量为num的物品,看j是否可以取到

放质量为num的物品,看j-num是否可以取到

只要上述两种情况任意为true,此时dp[j]就为true.

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        # [1,5,11,5] total_sum = 11
        # dp = [False] * 12
        # nums: 1 for i in range(11, 0, -1): dp[i] = dp[i] or dp[i-1] dp[1] = True
        # nums: 5 for i in range(11, 4, -1): dp[6] = dp[1] = True, dp[5] = dp[0] = True
        # nums: 11 for i in range(11, 10, -1): dp[11] = True
        # nums: 5 for i in range(11, 4, -1): dp[10] = dp[5] = True
        total_sum = sum(nums)

        if total_sum % 2 != 0:
            return False

        target_sum = total_sum // 2
        dp = [False] * (target_sum + 1)
        dp[0] = True

        for num in nums:
            # 从target_sum逆序迭代到num,步长为-1
            for j in range(target_sum, num - 1, -1):
                dp[j] = dp[j] or dp[j - num]
        return dp[target_sum]

1049.最后一块石头的重量II

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0

转化为背包问题:背包质量为sum(stones) // 2 向下取整,看最大可以放进多大质量的石头

则最后返回的石头重量为(sum(stones) - dp[-1][-1]) - dp[-1][-1]

dp[i][j]表示背包承重为j的情况下,在0-i块石头中任取,最多能放进多重的石头

二维动态数组

class Solution:
    def lastStoneWeightII(self, stones: List[int]) -> int:
        bagweight = sum(stones) // 2
        dp = [[0] * (bagweight + 1) for _ in range(len(stones))]

        for j in range(stones[0], bagweight+1):
            dp[0][j] = stones[0]
        
        for i in range(1, len(stones)):
            for j in range(bagweight+1):
                if j < stones[i]:
                    dp[i][j] = dp[i-1][j]
                else:
                    dp[i][j] = max(dp[i-1][j], dp[i-1][j-stones[i]] + stones[i])

        return sum(stones) - 2*dp[-1][-1]

一维动态数组

class Solution:
    def lastStoneWeightII(self, stones: List[int]) -> int:
        bagweight = sum(stones) // 2
        dp = [0] * (bagweight + 1)
        
        for i in range(len(stones)):
            for j in range(bagweight, stones[i] - 1, -1):
                dp[j] = max(dp[j], dp[j-stones[i]] + stones[i])

        return sum(stones) - 2*dp[-1]

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值