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背包问题还得再刷!