Leetcode
本题尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。
所以本题和416. 分割等和子集几乎一样。
不同的地方在于,想要得到最后剩下的石头的重量,return sum(stones) - dp[target] * 2
dp数组是True 或者False. dp[j]为j重量下能否正好由一个或多个石头组成
class Solution:
def lastStoneWeightII(self, stones: List[int]) -> int:
n = len(stones)
total = sum(stones)
target = total // 2
dp = [False] * (target + 1)
dp[0] = True
for stone in stones:
for j in range(target, stone - 1, -1):
dp[j] = dp[j] or dp[j-stone]
for j in range(target, -1, -1):
if dp[j]:
return total - 2 * j
简化
dp[j]表示为j重量下能放入重量(价值)最大石头。对于单独的石头,重量和价值相等。
class Solution:
def lastStoneWeightII(self, stones: List[int]) -> int:
total = sum(stones)
target = total // 2
dp = [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 - 2*dp[target]
O(m × n)
, m是石头总重量(准确的说是总重量的一半),n为石头块数O(m)
本质上这道题可以将nums
内的非负整数分成两组,一组left
为正整数,一组right
为将要被减去的数字。
我们做出如下推导:
left
+ right
= sum(nums)
left - right = target
=> right = left - target
left + (left - target) = sum(nums)
2*left = sum(nums) + target
left = sum(nums) + target // 2
这道题被分解成为了,已知sum(nums)
和target
,求nums
中的数字有几种能组合成left
的方式。所以,通过left = sum(nums) + target // 2
这个等式,我们将问题化简成01背包问题。
但是此题和前面的题目有一些区别,01背包求的是背包装入的最大价值,分割等和子集求的是能否装满背包,最后一块石头的重量求的是能装的最大重量(等同于能装的最大价值),此题求的是给定背包容量,有多少种把背包装满的方式。
二维dp
下图的neg可以视作我们定义的left
或者right
,不影响。
一维dp
可以看成是将二维dp压缩之后的结果
二维dp
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
total_sum = sum(nums) # 计算nums的总和
# 注意这里需要使用target的绝对值
if abs(target) > total_sum:
return 0 # 此时没有方案
if (target + total_sum) % 2 == 1:
return 0 # 此时没有方案
target_sum = (target + total_sum) // 2 # 目标和
# 创建二维动态规划数组,行表示选取的元素数量,列表示累加和
dp = [[0] * (target_sum + 1) for _ in range(len(nums) + 1)]
# 初始化状态, 0个元素中不选取的方法有一种
dp[0][0] = 1
# 动态规划过程
for i in range(1, len(nums) + 1):
for j in range(target_sum + 1):
dp[i][j] = dp[i - 1][j] # 不选取当前元素
if j >= nums[i - 1]:
dp[i][j] += dp[i - 1][j - nums[i - 1]] # 选取当前元素
return dp[len(nums)][target_sum] # 返回达到目标和的方案数
一维dp
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
total = sum(nums)
if abs(target) > total:
return 0
if (total + target) % 2 != 0:
return 0
left = (total + target) // 2
dp = [0] * (left + 1)
dp[0] = 1
for num in nums:
for j in range(left, num - 1, -1):
dp[j] += dp[j - num]
return dp[-1]
Leetcode
本题m
,n
相当于两个维度的01背包。
找最大子集相当于找 让这个2维的背包装满 最多能放多少个物品。
回想一下01背包的递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
,
这道题的递推公式为: dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1)
本题相当于多维dp数组压缩成2维了,所以遍历背包的时候需要倒序。
class Solution:
def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
# n, m 顺序可以对调,无所谓,因为是属于遍历背包的过程
dp = [[0] * (m + 1) for _ in range(n + 1)]
for s in strs:
zeros = s.count("0")
ones = s.count("1")
for i in range(n, ones - 1, -1):
for j in range(m, zeros - 1, -1):
dp[i][j] = max(dp[i][j], dp[i - ones][j - zeros] + 1)
return dp[n][m]
O(kmn),k
为strs
的长度O(mn)