0-1背包问题 之 分割等和子集以及变形问题
文章目录
- 416.分割等和子集
- 3489.零数组变换IV
0-1
背包问题就是选择与不选择的问题,从根本上来讲的话,就是一个可以不连续的子集target问题
,如果考虑的是一定得连续的问题,那么就可以考虑的是不定长滑动窗口的问题
416.分割等和子集
416.分割等和子集
- 分割为两个等和的子集,那么我们可以转化为只用选择一个子集的问题,那么剩余的子集就是总和减去当前子集的和
- 考虑到等和的问题,所以
首先求解出sum(nums)
,如果为奇数,那么肯定是不能成功分解的,如果是偶数,就可以转化为,在原本的nums数组中,能否找到和为target//2的组合?
- 注意
dp数组的初始化,dp[i][0]都是True
class Solution:
def canPartition(self, nums: List[int]) -> bool:
# 其实就是0-1背包问题,target 是一半,所以sum得是偶数
sumnums = sum(nums)
if sumnums % 2 == 1:
return False
target = sumnums // 2
n = len(nums)
# 现在的话就是一个经典的0-1背包问题
# 其实可以定义dp[i][j]表示为前i个物品中,是否可以组成j
dp = [[True]+[False]*target for _ in range(n)]
for i in range(n):
for j in range(1,target+1):
if j < nums[i] :
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i]]
return dp[n-1][target]
3489.零数组变换IV
3489.零数组变换IV
- 注意这个问题,我开始思考的是前缀和与差分,但是没考虑到题目中的
选择范围是[l,r]的范围中的子集进行操作
,那么就是说明对于每一个数的操作是独立的! - 只要我们在对应的
操作数组中能够得到这个nums[i]
的组合,那么就可以说明nums[i]
是可以组合成功的,所以,我们可以转化为这个416.分割等和子集
的问题,不过对应修改为能否在对应的pack[i]中得到nums[i]
的组合?
class Solution:
def minZeroArray(self, nums: List[int], queries: List[List[int]]) -> int:
# 别人做的太复杂了,能不能直接转化为这个 subnums的问题
n = len(nums)
# 就是得开n个背包
pack = [[] for _ in range(n)]
m = len(queries)
if sum(nums) == 0:
return 0
# 多个背包问题,然后求解的是是否都满足?都满足的情况下,找到最少的情况下的最大的那个所需的物品数
def check(num,target):
n1 = len(num)
dp = [[True]+[False]*target for _ in range(n1)]
for i in range(n1):
for j in range(1,target+1):
if j < num[i]:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = dp[i-1][j] or dp[i-1][j-num[i]]
if dp[i][target]:
return i+1
return -1
# 得到对应的一个物品的列表
for l,r,v in queries:
for i in range(n):
if l<= i <=r:
pack[i].append(v)
else:
pack[i].append(0)
minans = 0
for i in range(n):
# 每一个背包都进行遍历
cnt = check(pack[i],nums[i])
if cnt == -1:
return -1
else:
minans = max(minans,cnt)
return minans