背包问题。
之所以叫01背包,是因为每个物品只有拿或不拿两个状态。
做一个二维数组dp,i表示拿不拿第i个物品,j表示背包容量。每个节点两种情况:
遍历顺序是左上角到右下角(上图中手动画一根对角线)。
是不是很简单呢。
注意:之所以可以坍缩成一维,是因为二维中每一行i
都只与前一行i-1
相关。
牢记1:dp[j] = 容量为j的背包在前i个物品中挑选,能够装载的最大值。这里的i不在dp数组里,而是在外循环中。
牢记2:二维数组的做法要初始化第一行(i=0)和第一列(j=0),但是一维数组只需要初始化第一列就可以。i=0的情况包含在了i的取值范围中了。
转换问题(很关键):搜索和等于sum/2的子集,搜索到了就返回true。
可以用回溯+memoization,或者0-1背包bottom-up来做。每个元素的weight和value都是元素本身。
这题完全想不出怎么转换问题,无奈了。
转换问题:将石头尽量分成重量相同的两堆,这样对撞起来会剩得最少。
0-1背包,容量=sum(stones)//2。每个元素的weight和value都是元素本身。
这两道题其实第一眼很难做,第二眼也很难把它转换成标准的0-1背包来做。但是通过模板+每个元素的weight和value都是元素本身这样的settings,很轻松就可以写出来了。上面两题的套路完全一样,只不过最后对dp输出的处理,416是判断dp[-1][-1]是否等于target,1049是return sum-2*dp[-1][-1]。
另外还需要理解一维数组这个优化。优化之后代码量会显著减少。
附最佳代码:
416
def canPartition(self, nums: List[int]) -> bool:
if sum(nums) % 2:
return False
target = sum(nums) // 2
dp = [0] * (target+1)
for i in range(len(nums)):
for j in range(target, nums[i]-1, -1):
dp[j] = max(dp[j], dp[j-nums[i]]+nums[i])
return target == dp[-1]
1049
def lastStoneWeightII(self, stones: List[int]) -> int:
target = sum(stones) // 2
dp = [0] * (target+1)
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])
return sum(stones) - 2*dp[-1]
这题还是0-1背包,但是之前两题是背包能装多少东西,这一题是背包装东西的方案数量。
当然,转换问题的这步还是最重要的:
假设前面是负号的元素之和为neg
,那么前面是正号的元素之和为sum(nums)-neg
。因此有等式sum(nums)-neg-neg = target
. target和sum(nums)已知,所以问题转换为寻找和为(sum(nums)-target)/2
的子集数量。
设dp[i][j] = [0,i]当中和为j的子集数量。
递推公式为dp[i][j] = dp[i-1][j-nums[i]] + dp[i-1][j]
. (nums[i]在子集中+nums[i]不在子集中)
那么坍缩到一维数组,递推公式就为dp[j] += dp[j-nums[i]]
。
初始化的话,dp[0]应该是1,因为j=nums[i]的情况是有一个子集的。
def findTargetSumWays(self, nums: List[int], target: int) -> int:
if sum(nums) < abs(target) or (sum(nums)-target) % 2:
return 0
t = (sum(nums)-target)//2
dp = [1] + [0]*t
for i in range(len(nums)):
for j in range(t, nums[i]-1, -1):
dp[j] += dp[j-nums[i]]
return dp[-1]
0-1背包,但是装的东西有两个维度:0的数量和1的数量。
dp[j][k] = str[0:i]中,0的数量小于等于j,且1的数量小于等于k的所有子集中,元素最多的子集中的元素个数。
递推:dp[j][k] = max(dp[j-zeros][k-ones] + 1, dp[j][k]), 其中zeros,ones为当前处理元素的0和1的个数。
初始化:初始化为0就可以。
def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
def getnums(i):
c = Counter(strs[i])
return c["0"],c["1"]
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(len(strs)):
zeros, ones = getnums(i)
for j in range(m, zeros-1, -1):
for k in range(n, ones-1, -1):
dp[j][k] = max(dp[j-zeros][k-ones] + 1, dp[j][k])
return dp[-1][-1]