代码随想录算法训练营day43 | 1049. 最后一块石头的重量 II,494. 目标和,474.一和零

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

如何映射为01背包问题?

  • 其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了

  • 整体思路与416. 分割等和子集一样,区别在于最后step5 输出内容不一样

class Solution(object):
    def lastStoneWeightII(self, stones):
        """
        :type stones: List[int]
        :rtype: int
        """
        target = sum(stones)//2
        #step1: dp[j]表示为容量为j的背包,最多可以背dp[j]这么重的石头
        #step2: dp[j] = max(dp[j], dp[j-stones[i]]+stones[i])
        #step3: 初始化
        dp = [0]*1501 #30*100 // 2 + 1
        #step4: 遍历顺序:倒序,且先物品后背包
        for i in range(len(stones)):
            for j in range(target, stones[i]-1, -1):
                dp[j] = max(dp[j], dp[j-stones[i]]+stones[i])
                print(dp[:target+1])
                print('=======')
        #step5: 打印检查
        '''最后dp[target]里是容量为target的背包所能背的最大重量。那么分成两堆石头,一堆石头的总重量是
           dp[target],另一堆就是sum - dp[target]。在计算target的时候,target = sum / 2 因为是
           向下取整,所以sum - dp[target] 一定是大于等于dp[target]的。那么相撞之后剩下的最小石头重量
           就是 (sum - dp[target]) - dp[target]。
        '''
        return sum(stones)-dp[target]-dp[target]

494. 目标和

如何映射为01背包问题?

  • 既然是找target,那么就一定有(left组合 - right组合 = target),同时(left组合 + right组合 = sum);而sum是固定的且target是已知的,因此可以求出left = (target + sum)//2
  • 那么这道题就变成了在集合nums中找出和为left的组合。
class Solution(object):
    def findTargetSumWays(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        if abs(target) > sum(nums):
            return 0
        bagSize = (sum(nums) + target) // 2 #此时需注意:只要出现 / 运算符,就要想到向下取整的情况是否有影响。
        if (sum(nums) + target) % 2 != 0:
            return 0

        #step1: dp[j]表示:填满j(包括j)这么大容积的包,有dp[j]种方法
        #step2: 递推公式:dp[j] = dp[j] + dp[j-nums[i]]
            #不考虑nums[i]的情况:填满容量为j的背包,有dp[j]种方法(即二维数组状态下的dp[i-1][j])。
            #考虑nums[i]的情况:凑成dp[j]就有dp[j - nums[i]] 种方法(即二维数组状态下的dp[i-1][j-nums[i]])。
        #step3: 初始化
        dp = [0]*(bagSize+1)
        dp[0] = 1 #装满容量为0的背包,有1种方法,就是装0件物品。
        #step4: 遍历顺序:倒序且先物品后背包
        for i in range(len(nums)):
            for j in range(bagSize, nums[i]-1, -1):
                dp[j] = dp[j] + dp[j-nums[i]]
        #step5: 打印检查
        return dp[bagSize]

参考理解递推公式:代码随想录43——动态规划:1049最后一块石头的重量II、494目标和、474一和零_Cc1924的博客-CSDN博客

dp[i][j]中存储的是使用0到i的所有元素,正好放满容量为j的背包的所有可能的组合的个数。如果不加入当前的元素nums[i]的话,则就相当于仍然使用0到i-1的所有元素正好放满容量为j的背包得到的所有可能的组合的个数,即dp[i-1][j];如果加入当前元素nums[i]的话,那么可能的组合个数就是使用前面0到i-1的所有元素放满容量为j - nums[i]的背包得到的所有的可能的组合的个数,也就是dp[i-1][j-nums[i]]。由于求的是方案的总数,所以递推公式是

dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]。再压缩成一维数组,即dp[j] = dp[j] + dp[j-nums[i]]
 

474.一和零

如何映射为01背包问题?

  • m and n可以看作是二维背包的两个维度(虽然此时dp数组需要定义成一个二维数组,但是对应的仍然是一位数组的情况,参考递推公式那一步)
  • 不同长度的字符串就是不同大小的待装物品
class Solution(object):
    def findMaxForm(self, strs, m, n):
        """
        :type strs: List[str]
        :type m: int
        :type n: int
        :rtype: int
        """
        #step1: dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]
        #step2: 递推公式:
        '''
        dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。
        dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。
        然后在遍历的过程中,取dp[i][j]的最大值。
        所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
        '''
        #step3: 初始化
        dp = [[0 for _ in range(n+1)] for _ in range(m+1)]
        #step4: 遍历顺序:倒序且先物品后背包(物品就是strs里的字符串,背包容量就是题目描述中的m和n)
        for str in strs:
            zeros = str.count('0')
            ones = str.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)
        #step5: 打印检查
        return dp[m][n]



小结:

  • 如何确定dp的数组的含义?

    • 简单来说就是题目求的是什么,dp数组就怎么定义。
  • 在求装满背包有几种方法的情况下,递推公式一般为

    • dp[j] = dp[j] + dp[j-nums[i]]

  • 这几道01背包问题还得再刷!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值