1049. 最后一块石头的重量 II
思路:
参考分隔等和子集,本题试图凑出sum//2,如果凑出来,y-x=y-y=0,如果凑不出来,就取dp[n][target]时候能凑出的最大值
class Solution:
def lastStoneWeightII(self, stones: List[int]) -> int:
# 计算背包容量
n = len(stones)
total = sum(stones)
target = total // 2 # 另一堆是total-target>target
# dp定义和初始化:dp[i][j]表示前i堆石头是否有子集恰好装满容量为j的背包
dp = [[False] * (target+1) for _ in range(n+1)]
# 背包容量为0时候,空集能装满
for i in range(n+1):
dp[i][0] = True
# 遍历石头
for i in range(1, n+1):
for j in range(1, target+1):
if j < stones[i-1]:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = dp[i-1][j] or dp[i-1][j-stones[i-1]]
# 逆序遍历,找到第一个装满的最大容量j
for j in range(target,-1,-1):
if dp[n][j]:
return total - 2 * j # 最大容量为j,恰好被装满
return 0
滚动数组:
class Solution:
def lastStoneWeightII(self, stones: List[int]) -> int:
# 计算背包容量
n = len(stones)
total = sum(stones)
target = total // 2 # 另一堆是total-target>target
# dp定义和初始化:dp[i][j]表示前i堆石头是否有子集恰好装满容量为j的背包
dp = [0] * (target+1)
# 背包容量为0时候,空集能装满
dp[0] = True
# 遍历石头
for i in range(n):
for j in range(target, stones[i-1]-1,-1): # 可以j=stones[i-1]
dp[j] = dp[j] or dp[j-stones[i-1]]
# 逆序遍历,找到第一个装满的最大容量j
for j in range(target,-1,-1):
if dp[j]:
return total - 2 * j # 最大容量为j,恰好被装满
return 0
494. 目标和
思路1:回溯
回溯,每个位置有+elem -elem两种选择
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
self.target = target
self.nums = nums
self.count = 0 # 满足target的表达式数量
self.backtrack(0, 0)
return self.count
def backtrack(self, index: int, current_sum: int)->None:
# base case
if index == len(self.nums):
if current_sum == self.target:
self.count += 1
return
# +1 or -1
self.backtrack(index+1, current_sum + self.nums[index])
self.backtrack(index+1, current_sum - self.nums[index])
剪枝:
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
self.target = target
self.nums = nums
self.count = 0 # 满足target的表达式数量
self.memo = {} # 备忘录
self.backtrack(0, 0)
return self.count
def backtrack(self, index: int, current_sum: int) -> None:
# 备忘录:检查当前状态是否已经计算过
if (index, current_sum) in self.memo:
self.count += self.memo[(index, current_sum)]
return
# base case
if index == len(self.nums):
if current_sum == self.target:
self.count += 1
self.memo[(index, current_sum)] = 1
else:
self.memo[(index, current_sum)] = 0
return
# 剪枝
remaining_sum = sum(self.nums[index:])
if current_sum + remaining_sum < self.target or current_sum - remaining_sum > self.target:
self.memo[(index, current_sum)] = 0
return
# +1 or -1
original_count = self.count
self.backtrack(index + 1, current_sum + self.nums[index])
self.backtrack(index + 1, current_sum - self.nums[index])
self.memo[(index, current_sum)] = self.count - original_count
思路2:dp
- 将问题转化为01背包问题
- 设所有数字和为sum,添加正号的数字和为x,则添加负号的数字和为sum-x
- 根据题目要求:x - (sum-x) = target
- 可得:x = (target + sum)/2
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
# 0-1背包
total = sum(nums)
n = len(nums)
# base case: target+total能被2整除;target不能大于total
if abs(target) > total:
return 0 # 0种方法
if (total+target)%2 !=0:
return 0
# dp初始化:dp[j]表示装满容量为j的背包有多少种方法
bag_size = (total + target) //2
dp = [0] * (bag_size + 1)
dp[0] = 1 # 空包时候空集算1种方法
# 迭代
for i in range(n):
for j in range(bag_size, nums[i-1]-1, -1):
dp[j] += dp[j-nums[i-1]]
return dp[bag_size]
474. 一和零
思路:
定义:dp[i][j]表示在i个0j个1的条件下,最多的字符串数量
背包容量:m个0n个1
物品:字符串
物品重量:字符串有多少个0和多少个1
物品价值:1,求得是字符串的数量,每个字符串价值为1
初始化:dp[0][0]都是0吧
递推:
不取当前字符:dp[i][j] 不变
取当前字符:dp[i][j] = dp[i-zerosNum][j-onesNum] + 1
遍历方向:顺序遍历每个字符串;逆序遍历容量m和n,防止重复计算物品(0-1背包特点)
举例:
输入: strs = ["10", "0", "1"], m = 1, n = 1
输出: 2
解释: 最大的子集是 {"0", "1"} ,所以答案是 2 。
dp = [[0,0],[0,0]]
迭代字符1: "10"
zerosNum = 1
onesNum = 1
i=1;j=1;
dp[1][1] = max(dp[1][1], dp[1-1][1-1]+1) = max(0, 1) = 1
迭代字符串2: "0"
zerosNum = 1
onesNum = 0
i=1;j=1;
dp[1][1] = max(dp[1][1], dp[1-1][1-0]+1) = max(1, 1) = 1
i=1;j=0;
dp[1][0] = max(dp[1][0], dp[1-1][0-0]+1) = max(0, 1) = 1
迭代字符串3: "1"
zerosNum =0
onesNum = 1
i=1;j=1;
dp[1][1] = max(dp[1][1], dp[1-0][1-1]+1) = max(1, 2) = 2
i=0;j=1;
dp[0][1] = max(dp[0][1], dp[0-0][1-1]+1) = max(0, 1) = 1
dp[1][1] = 2
注意点:
- 理解每个字符串是一个物品
- m和n是两个维度的背包容量
- 需要倒序遍历背包容量
class Solution:
def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
# dp数组
dp = [[0] * (n+1) for _ in range(m+1)]
# 遍历每个字符
for c in strs:
# 统计0和1的数量
zerosNum = c.count("0")
onesNum = c.count("1")
# 逆序遍历容量m和n
for i in range(m, zerosNum-1, -1):
for j in range(n, onesNum-1, -1):
dp[i][j] = max(dp[i][j], dp[i-zerosNum][j-onesNum] + 1)
return dp[m][n]
时间复杂度: O(kmn), k是字符串数组长度
空间复杂度: O(mn)