代码随想录训练营第43天 | 1049. 最后一块石头的重量 II ● 494. 目标和 ● 474.一和零

文章讲述了如何用动态规划解决LeetCode中的三个问题:最后一块石头的重量II、目标和和一和零,这三个问题本质上都是背包问题,通过推导得出相应的动态规划公式并分析了时间复杂度和空间复杂度。
摘要由CSDN通过智能技术生成

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

题目链接:https://leetcode.com/problems/last-stone-weight-ii/

解法:

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

但是需要有个思路的推导:计算所有石头的总重量,然后定义两个背包,两个背包的最大容量总和为所有石头的总总量,而第一个背包的最大容量等于第二个的,或者比第二个小1个单位。这样的话,如果第一个背包已经装满了(再放新的放不下了),那么此时两个背包之差就是相撞后剩下的最小。

所以代码其实改动不大,也就是最后的结果为 (sum - dp[target]) - dp[target]。

初始化的时候,注意dp[1] 并不是等于1,因为可能没有重量为1的石头。

边界条件:无

时间复杂度:O(m × n) , m是背包中的石头总重量(准确的说是总重量的一半),n为石头块数

空间复杂度:O(m)

class Solution(object):
    def lastStoneWeightII(self, stones):
       
        total_sum = sum(stones)
        target = total_sum // 2
        # 容量为i的包最大能装的数量
        # dp[1] 并不是等于1,因为可能没有重量为1的石头
        dp = [0] * (target + 1)
        
        for weight in stones:
            for j in range(target, weight-1, -1):
                dp[j] = max(dp[j], dp[j - weight] + weight)

        # 注意第二个包装的重量是 sum - dp[-1] 而不是 sum - target
        return total_sum - 2 * dp[-1]

494. 目标和 

题目链接:https://leetcode.com/problems/target-sum/

解法:

这个题是一个组合问题了。

既然为target,那么就一定有 left组合 - right组合 = target。left + right = sum,而sum是固定的。right = sum - left。公式来了, left - (sum - left) = target 推导出 left = (target + sum)/2 。target是固定的,sum是固定的,left就可以求出来。

此时问题就是在集合nums中找出和为left的组合。left组合确定,right组合也确定了,所以不是 left组合的个数乘以right组合的个数,而是只需要left组合的个数。

1. 确定dp数组以及下标的含义

dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法。

2. 确定递推公式

只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法。于是把所有的 dp[j - nums[i]] 累加起来。

3. 初始化

在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递推结果将都是0。

另外边界条件需要注意,(target + sum) / 2 不能整除则无解,另外abs(target) > sum 也无解(target可以为负数)。

边界条件:

时间复杂度:O(n × m),n为正数个数,m为背包容量

空间复杂度:O(m),m为背包容量

class Solution(object):
    def findTargetSumWays(self, nums, target):
        total_sum = sum(nums)
        if (total_sum + target) % 2 == 1:
            return 0
        if abs(target) > total_sum:
            return 0
        bag_size = (total_sum + target) // 2
        # dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
        # dp[0]初始化为1,这点很难想到
        dp = [0] * (bag_size + 1)
        dp[0] = 1 
        for num in nums:
            for j in range(bag_size, num-1, -1):
                dp[j] += dp[j-num]
        return dp[-1]

474.一和零 

题目链接:https://leetcode.com/problems/ones-and-zeroes

解法:

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

1. 确定dp数组(dp table)以及下标的含义

dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。最后需要求的就是dp[m][n]。但这还是一维数组的01背包问题,而不是二维数组的。因为不包含物品的维度。

2. 确定递推公式

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)。

回想一下01背包的递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

对比一下就会发现,字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])。

3. 确定遍历顺序

由于是一维数组的01背包问题,一定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历!那么本题也是,物品就是strs里的字符串,背包容量就是题目描述中的m和n。

边界条件:无

时间复杂度:O(kmn),k 为strs的长度

空间复杂度:O(mn)

class Solution(object):
    def findMaxForm(self, strs, m, n):
        dp = [[0] * (n+1) for _ in range(m+1)]
        for str_ in strs:
            zero_num = str_.count("0")
            one_num = str_.count("1")
            for i in range(m, zero_num-1, -1):
                for j in range(n, one_num-1, -1):
                    dp[i][j] = max(dp[i][j], dp[i-zero_num][j-one_num]+1)
        return dp[m][n]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值