1049.最后一块石头的重量II
文档讲解:代码随想录
题目链接:. - 力扣(LeetCode)
本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。
和昨天讲解的416. 分割等和子集 (opens new window)非常像了。
本题物品的重量为stones[i],物品的价值也为stones[i]。
对应着01背包里的物品重量weight[i]和 物品价值value[i]。
背包的容量为所有石块的重量的一半
尽量凑,找到背包中能装下的最大值,
接下来进行动规五步曲:
确定dp数组以及下标的含义
dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背最大重量为dp[j]。
可以回忆一下01背包中,dp[j]的含义,容量为j的背包,最多可以装的价值为 dp[j]。
相对于 01背包,本题中,石头的重量是 stones[i],石头的价值也是 stones[i] ,可以 “最多可以装的价值为 dp[j]” == “最多可以背的重量为dp[j]”
确定递推公式
01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
本题则是:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
一些同学可能看到这dp[j - stones[i]] + stones[i]中 又有- stones[i] 又有+stones[i],看着有点晕乎。
大家可以再去看 dp[j]的含义。
dp数组如何初始化
既然 dp[j]中的j表示容量,那么最大容量(重量)是多少呢,就是所有石头的重量和。
因为提示中给出1 <= stones.length <= 30,1 <= stones[i] <= 1000,所以最大重量就是30 * 1000 。
而我们要求的target其实只是最大重量的一半,所以dp数组开到15000大小就可以了。
当然也可以把石头遍历一遍,计算出石头总重量 然后除2,得到dp数组的大小。
接下来就是如何初始化dp[j]呢,因为重量都不会是负数,所以dp[j]都初始化为0就可以了,这样在递归公式dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);中dp[j]才不会初始值所覆盖。
遍历顺序
倒叙遍历
最后结果推导
最后dp[target]里是容量为target的背包所能背的最大重量。
那么分成两堆石头,一堆石头的总重量是dp[target],另一堆就是sum - dp[target]。
在计算target的时候,target = sum / 2 因为是向下取整,所以sum - dp[target] 一定是大于等于dp[target]的。
那么相撞之后剩下的最小石头重量就是 (sum - dp[target]) - dp[target]。
class Solution:def lastStoneWeightII(self, stones: List[int]) -> int:#整个石头的重量可能不是2的倍数?,向下取整total_sum = sum(stones)target = total_sum//2dp = [0] * (target+1)for stone in stones:#注意循环的范围for j in range(target,stone-1,-1):dp[j] = max(dp[j],dp[j-stone]+stone)##最后的返回值为什么是这样的return total_sum-2*dp[-1]
难点:怎么转化为是01背包问题
补充:
-
%(取模运算符):
- 功能:计算两个数相除的余数。
- 例子:
7 % 3
的结果是1
,因为7 除以 3
的余数是1
。
-
/(除法运算符):
- 功能:计算两个数相除的商,结果是浮点数。
- 例子:
7 / 3
的结果是2.3333333333333335
,因为7 除以 3
是2.333...
。
-
//(整数除法运算符):
- 功能:计算两个数相除的商,结果是整数(向下取整)。
- 例子:
7 // 3
的结果是2
,因为7 除以 3
是2.333...
,向下取整得到2
。
494.目标和
文档讲解:代码随想录
题目链接:. - 力扣(LeetCode)
和之前的组合总和有点像,后面对比一下
放加法的是一个集合(left),减法是一个集合(right)
left组合 - right组合 = target。
left + right = sum,而sum是固定的。right = sum - left
公式来了, left - (sum - left) = target 推导出 left = (target + sum)/2 。(如果不能整除,说明题目中没有满足的组合)
target是固定的,sum是固定的,left就可以求出来。
此时问题就是在集合nums中找出和为left的组合。也就是装满容量为left的容器有多少方法
纯01背包问的是装满这个背包,它的最大价值是多少,分割等和子集问的是能不能装满这个背包,最后一块石头的重量是给我们一个背包,让我们尽量去装,而本题问的是给一个背包的容量,有多少种方法装满,这三道题都是01背包在不同层面上的应用
确定dp数组以及下标的含义
dp[j]装满容量为j的背包有多少种方法
确定递推公式
有哪些来源可以推出dp[j]呢?
只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法。
例如:dp[j],j 为5,
- 已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
- 已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
- 已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 容量为5的背包
- 已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 容量为5的背包
- 已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 容量为5的背包
那么凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来。
所以求组合类问题的公式,都是类似这种:
dp[j] += dp[j - nums[i]]
dp数组如何初始化(不懂)
从递推公式可以看出,在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递推结果将都是0。
这里有录友可能认为从dp数组定义来说 dp[0] 应该是0,也有录友认为dp[0]应该是1。
其实不要硬去解释它的含义,咱就把 dp[0]的情况带入本题看看应该等于多少。
如果数组[0] ,target = 0,那么 bagSize = (target + sum) / 2 = 0。 dp[0]也应该是1, 也就是说给数组里的元素 0 前面无论放加法还是减法,都是 1 种方法。
遍历顺序
01背包倒序遍历
class Solution:def findTargetSumWays(self, nums: List[int], target: int) -> int:total_sum = sum(nums)if (total_sum+target) %2 !=0 :return 0if abs(target)> total_sum:return 0left = (total_sum+target) // 2dp=[0] *(left+1) #装满left背包有多少方法dp[0]=1 #为什么要这样初始化?for num in nums:for j in range(left,num-1,-1):dp[j] += dp[j-num]return dp[left]
474.一和零
文档讲解:代码随想录
题目链接:. - 力扣(LeetCode)
本题中strs 数组里的元素就是物品,每个物品都是一个!
而m 和 n相当于是一个背包,两个维度的背包。
m和n就是形容一种容器,装满这个容器最多有多少个元素就输出多少个
理解成多重背包的同学主要是把m和n混淆为物品了,感觉这是不同数量的物品,所以以为是多重背包。
但本题其实是01背包问题!
只不过这个背包有两个维度,一个是m 一个是n,而不同长度的字符串就是不同大小的待装物品。
容器有两个维度,物品也是有两个维度,x个0,和y个1,重量就是[x,y]
确定dp数组以及下标的含义
二维dp数组
dp[m][n] :装满m个0,n个1最多背了多少个物品
确定递推公式
两个维度,放不放当前物品(x,y)
dp[i][j] = max(dp[i][j], dp[i-x][j-y] + 1)
dp数组如何初始化
dp[0][0] = 0
非0下标也初始为0
遍历顺序
先遍历物品,再遍历背包,背包倒序遍历
class Solution:def findMaxForm(self, strs: List[str], m: int, n: int) -> int:dp = [[0 for _ in range(n+1)] for _ in range(m+1)]#遍历物品for str in strs:x = 0y = 0for char in str:if char == '0':x+=1else:y += 1#遍历背包for i in range(m,x-1,-1): #存放0的个数,如果写为0,结果会不对for j in range(n,y-1,-1):#存放1的个数dp[i][j] = max(dp[i][j],dp[i-x][j-y]+1)return dp[m][n]