Day43代码随想录动态规划part05:1049.最后一块石头的重量II、494.目标和、474.一和零

Day43 动态规划part05-01背包问题

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

leetcode题目链接1049. 最后一块石头的重量 II - 力扣(LeetCode)

题意:有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

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

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

**思路:类似分割等额子集!我们想把这些分为尽可能总和相等的两堆!**本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了

一维dp版本:

  • dp[j]数组的含义:容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背最大重量为dp[j]。01背包中,dp[j]的含义,容量为j的背包,最多可以装的价值为 dp[j]。相对于 01背包,本题中,石头的重量是 stones[i],石头的价值也是 stones[i] ,可以 “最多可以装的价值为 dp[j]” == “最多可以背的重量为dp[j]”
  • **递推公式:dp[j] = max(dp[j], dp[j-weight[i]] + value[i])**前一种是没有选择该物品i的情况,后一种是选择i的情况。本题种就是weight和value都是stone的重量
  • 初始化:都初始化成0,要不然可能会在递推的时候把他们覆盖;我们定义一个sum/2的(向下取整)
  • 遍历顺序:先for遍历物品;再for遍历背包,而且是从大往小遍历(反向遍历)
  • 最后结果是(sum-dp[target])-dp[target], 因为是向下取整所以dp[target]一定是小于等于另外一部分石头的
class Solution:
    def lastStoneWeightII(self, stones: List[int]) -> int:
        n = len(stones)
        sum_all = sum(stones)
        target = sum_all // 2
        print(sum_all, target)
        dp = [0] * (target+1)
        for i in range(n): #遍历物品
            for j in range(target, stones[i]-1, -1):
                # print(j, stones[i]-1)
                dp[j] = max(dp[j], dp[j-stones[i]] + stones[i])
            print(dp)
        return (sum_all - dp[-1]) - dp[-1]

494.目标和

leetcode题目链接. - 力扣(LeetCode)

题意:给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:

  • 输入:nums: [1, 1, 1, 1, 1], S: 3
  • 输出:5

思路:假如本题中目标值为target,左集合left为包含左集合元素的总和,右集合right也是同样,那么left + right = sum,同时还希望left - right = target,由此推导出 2 * left = (target + sum)/2,这里target和sum都是固定的,所以可以求出left

此时问题就转化为,装满容量为x的背包,有几种方法

  • dp[j]数组含义:和为j时有多少种组合方式,装满容量为j的背包有有几种方法
  • 递推公式:只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法。例如
    • 已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
    • 已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
    • 已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 容量为5的背包
    • 已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 容量为5的背包
    • 已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 容量为5的背包
    • 所以最终时dp[j] += dp[j-nums[i]]
  • 初始化:dp[0] = 1因为装满为target = 0的背包有1种组合,要不后序没法递推下去
  • 遍历顺序:对于01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。
class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        sum_all = sum(nums)
        if abs(target) > sum_all:
            return 0
        if (sum_all + target)%2 !=0:
            return 0
        left = (sum_all + target)//2
        dp = [0] * (left+1)
        dp[0] = 1
        for num in nums:
            for j in range(left, num-1, -1):
                dp[j] += dp[j-num]
            # print(dp)
        return dp[left]
# 示例输入:[1,1,1,1,1], target = 3
dp数组:
[1, 1, 0, 0, 0]
[1, 2, 1, 0, 0]
[1, 3, 3, 1, 0]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5]

474.一和零

leetcode题目链接:. - 力扣(LeetCode)

题意:给你一个二进制字符串数组 strs 和两个整数 m 和 n 。请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

思路:

多重背包是每个物品,数量不同的情况。

本题中strs 数组里的元素就是物品,每个物品都是一个!而m 和 n相当于是一个背包,两个维度的背包

理解成多重背包的同学主要是把m和n混淆为物品了,感觉这是不同数量的物品,所以以为是多重背包。但本题其实是01背包问题!

  • 确定dp数组(dp table)以及下标的含义:dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]
  • 递推公式:如果一个str种有a个0和b个1,那么dp[i][j] = max(dp[i][j], dp[i-a][j-b] +1)
  • 初始化:因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。
  • 确定遍历顺序:01背包为什么一定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历!

总结0-1背包:此时我们讲解了0-1背包的多种应用,

class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        dp = [[0]*(n+1) for _ in range(m+1)]
        for s in strs:
            zeros = s.count('0') #掌握这种字符串计算方式
            ones = s.count('1')
            for i in range(m, zeros-1, -1):
                for j in range(n, ones-1, -1):
                    dp[i][j] = max(dp[i][j], dp[i-zeros][j-ones]+1)
            # print(dp)
        return dp[m][n] 

  • 12
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值