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]