代码随想录算法训练营第三十六天 | 1049.最后一块石头的重量 Ⅱ 494.目标和 474.一和零

LeetCode 1049.最后一块石头的重量Ⅱ:

文章链接
题目链接:1049.最后一块石头的重量 Ⅱ

思路:

  1. 01背包(dp为价值)
    只要将石头分成重量近似相等的两堆,然后一起粉碎即可得到最小可能重量,从而思路与前面分割子集和相似。
    本题中物品重量和物品价值都是stones[i]
    动态规划五部曲
  • ① dp数组及下标含义
    dp[j]为背包容量为 j 的背包所能装的最大价值
  • ② 递推公式:
    dp[j] = max(dp[j], dp[j - stones[i]] + stones[i])
  • ③ 初始化
    j 的范围为[0, sum // 2],dp数组的大小为(sum // 2 + 1),
    因为石头的重量都是正整数,因此dp初始化为0,避免初始值影响递推后的结果
  • ④ 遍历顺序
    先物品后背包容量,且背包容量为逆序遍历
  • ⑤ 举例
    在这里插入图片描述
    那么石头堆可以分成重量为dp[target]和(_sum - dp[target])的两堆,因为 _sum // 2是向下取整,因此 _sum - dp[target] > dp[target]
    因此相撞之后剩下的最小石头重量为( _sum - dp[target]) - dp[target]
class Solution:
    def lastStoneWeightII(self, stones: List[int]) -> int:
        _sum = sum(stones)
        target = _sum // 2
        print("target: " + str(target) + "_sum: " + str(_sum))
        dp = [0] * (target + 1) # 初始化为全0
        for stone in stones:
            for j in range(target, stone - 1, -1):
                dp[j] = max(dp[j], dp[j - stone] + stone)
            print(dp[target])

        return _sum - dp[target] - dp[target]
        
  1. 01背包(dp为True or False)
    石头堆是否可以分成重量相等的两堆,如果不行,那么去除一小部分(不一定是完整的石头)后是否可以分成重量相等的两堆,此时去除的一小部分就是相撞后剩下的最小石头重量
    动态规划五部曲
  • ① dp数组及下标含义
    dp[j] : 能否从石头堆中取出部分石头,使其重量和恰好为 j
  • ② 递推
    根据 第 i 个物品是取还是不取
    如果不取第 i 个物品,dp[j] = dp[j]
    如果取第 i 个物品 dp[j] = dp[j - stones[i]]
    dp[j]只要上面两个一个成立即可
    dp[j] = dp[j] or dp[j - stones[i]]
  • ③ 初始化
    j 的范围为[0, sum // 2],因此dp数组的大小为(sum // 2 + 1)
    dp[0]初始化为True,因为石头堆中都不取石头来满足重量和恰好为0,其余非0初始化为False,避免初始值对递推的结果产生影响
  • ④ 遍历顺序
    01背包的遍历顺序,先物品后背包容量,逆序遍历背包容量
  • ⑤ 举例
    在这里插入图片描述
    dp[j] = True表示可以从石头中拿出石头且总重量为j,因此碰撞后最小的石头重量为 _sum - j * 2
class Solution:
    def lastStoneWeightII(self, stones: List[int]) -> int:
        target_sum = sum(stones)
        target = target_sum // 2
        dp = [False] * (target + 1)
        dp[0] = True
        for stone in stones:
            for j in range(target, stone - 1, -1):
                dp[j] = dp[j] or dp[j - stone]
        for j in range(target, -1, -1):
            if dp[j]:
                return target_sum - 2 * j
        return 0

感悟:

怎么将问题转换为01背包问题


LeetCode 494.目标和:

文章链接
题目链接:494.目标和

思路:

  1. 二维动态规划
    将题目转换为向背包中放数据
    可以将题目中添加了“+”和“-”的数按照“+”和“-”分成两类划归到一起,从而对非负整数数组加符号运算后为target的式子如下:
    left - right = target(如 (1 + 1+ 1) - (1 + 1) = 1)
    又left + right = sum
    可得left = (sum + target) // 2
    因此,当abs(target) > sum时是没有方案的
    (sum + target) % 2 == 1也是没有方案的。
    因此题目可以转换为从数组中取值存放在背包中,一共有多少种办法使得放入背包中的总价值恰好为 j
    物品的重量和价值均为nums[i],背包最大重量为left
    动态规划五部曲
  • ① dp数组及下标含义
    dp[i][j]表示从[0, i]中取物品,使得价值总和恰好为 j 的种类为dp[i][j]
  • ② 递推式
    还是根据是否取第 i 个物品来分类
    1)如果取第 i 个物品, 那么背包剩下的需要满足的价值为j - nums[i],价值总和恰好为 j 的种类有dp[i - 1][j - nums[i]]
    2)如果不取第 i 个物品,价值总和恰好为 j 的种类有dp[i - 1][j]种
递推式为dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]]
当 j >= nums[i]时成立。
如果 j < nums [i], dp[i][j] = dp[i - 1][j]
  • ③ 初始化
    由递推式可知,dp[i][j]由dp[i - 1][j]和dp[i - 1][j - nums[i]]得到,即需要左边和上边的数据。因此需要初始化第0行和第0列
初始化第0行,dp[0][0] = 1,
第0行只有 j = nums[i]的方法才为1,其余的要么装不满,要么装过了
dp[0][nums[i]] = 1

初始化第0列
因为非负整数可能出现0,如果有2个0元素,分别为元素1和元素2,那么dp[i][0]的种类应当有:
都不选
选元素1
选元素2
选元素1和2
一共4 = 2** 2种
zero = 0
for i in range(len(nums)):
	if nums[i] == 0:
		zero += 1
	dp[i][0] = 2** zero
  • ④ 遍历顺序
    因为是二维数组,所以遍历顺序可以先物品后背包容量,也可以先背包容量后物品,且顺序和逆序均可
  • ⑤ 举例:
    在这里插入图片描述
    dp[-1][-1]即为题目所要求的数目
class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        _sum = sum(nums)
        if abs(target) > _sum:   # 没有方案
            return 0
        if (target + _sum) % 2 == 1:    # 没有方案
            return 0
        maxj = (target + _sum) // 2
        dp = [[0] * (maxj + 1) for _ in range(len(nums))]
        # 初始化
        if nums[0] <= maxj:     # 初始化最上面一行
            dp[0][nums[0]] = 1
        dp[0][0] = 1
        zero = 0    # 初始化最左边一列
        for i in range(len(nums)):
            if nums[i] == 0:
                zero += 1
            dp[i][0] = 2 ** zero
        
        # 递推
        for i in range(1, len(nums)):
            for j in range(1, maxj + 1):
                if j >= nums[i]:
                    dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]]
                else:
                    dp[i][j] = dp[i - 1][j]

        return dp[-1][-1]
  1. 一维动态规划
    实际上和前面的01背包从二维到一维相同,递推式不同。
    dp[j] = dp[j] + dp[j - nums[i]]
    动态规划五部曲
  • ① dp数组及下标含义
    dp[j]表示使得价值总和恰好为 j 的种类为dp[j]
  • ② 递推式
    dp[j] = dp[j] + dp[j - nums[i]]
  • ③ 初始化
    dp[0] = 1,其余非0初始化为0避免初始值影响递推的结果
  • ④ 遍历顺序
    先物品后背包容量,且背包容量为逆序遍历
  • ⑤ 举例:
    在这里插入图片描述
class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        target_sum = sum(nums)
        if abs(target) > target_sum:    # 没有方案
            return 0
        if (target + target_sum) % 2 == 1:  # 没有方案
            return 0
        maxj = (target + target_sum) // 2
        dp = [0] * (maxj + 1)
        dp[0] = 1   # 初始化
        for num in nums:
            for j in range(maxj, num - 1, -1):
                dp[j] += dp[j - num]
        return dp[maxj]

LeetCode 474.一和零:

文章链接
题目链接:474.一和零

思路

本题仍然是01背包问题,只是背包容量从一维变为了二维n, m,因此采用二维dp数组分别记录n 和m(相当于前面的dp[j])
物品为字符串,其重量和价值均为其0和1的个数,背包最大容量为n 和 m
动态规划五部曲

  • ① dp数组及下标含义
    dp[i][j] : strs中最多有 i 个0和 j 个1的最大子集长度
  • ② 递推
    根据是否选择当前 s 来分类
    1)不选择s:dp[i][j] = dp[i][j]
    2)选择s,s中0和1的个数分别为zeroNums和oneNums,dp[i][j] = dp[i - zeroNums][ j - oneNums]
    因为要求最大子集长度,因此是求max
    dp[i][j] = max(dp[i][j], dp[i - zeroNums][ j - oneNums])
  • ③ 初始化
    dp数组的大小为(n + 1) * (m + 1)(m和n可以交换)。dp[0][0] = 0,因为是求max且0和1的个数最小为0,因此其余非0初始化为0避免初始值影响递推的结果。(可以理解为集合为空集时的dp数组)
  • ④ 遍历顺序
    先遍历物品,即字符串,再逆序遍历背包容量即n, m,其中n和m的遍历先后无所谓,但是要是逆序遍历
  • ⑤ 举例
    在这里插入图片描述
class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        dp = [[0] * (n + 1) for _ in range(m + 1)]  # 初始化二维dp数组
        # 先遍历物品,再逆序遍历背包
        for s in strs:
            zeroNums = s.count('0')
            oneNums = len(s) - zeroNums
            # 逆序遍历背包
            for i in range(m, zeroNums - 1, -1):
                for j in range(n, oneNums - 1, -1):
                    dp[i][j] = max(dp[i][j], dp[i - zeroNums][j - oneNums] + 1)
        return dp[m][n]
        

学习收获:

首先将题目转换为01背包问题,如背包最大价值,以及背包最大种类。转换时明确物品、物品重量价值以及背包最大容量应当为多少(比如目标和)
多维度背包容量的解法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值