目录
- 题目截图
- 题目分析
- ac code
- 总结
题目截图
题目分析
- 先特判,如果sum(nums) < 2 * k显然不可能成功!返回0
- 出现Mod大概率就是dp
- 1000的话提示我们用平方复杂度的dp
- 这种取子序列的问题,本质就是选or不选的问题
- 如果我们只考虑一维dp,dp[i]肯定是选前i个中能选的个数是吧
- 但是呢。。这个能选或者不能选其实是依赖之前选的和才行的
- 所以,我们必须要多加一维:dp[i][j]表示前i个选择的和为j的个数
- 然而,如果我们考虑好数组的话,它的和可以野蛮生长到10 ** 9不符合预期
- 因此,考虑反面,也就是坏数组
- 坏数组就是第一个or第二个数组的和小于k
- 由于对称性,我们只考虑第一个,然后答案*2即可
- 特别地,我们的第一维度要多加一个i = 0的表示起始的空集
- 初始条件就是dp[0][0] = 1意思是啥也没有的时候,和是0的话,有一个解法
- 然后dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]] 如果后者不小于0的话即可
- 最后返回dp[n]的和,这个就是所有坏数组的个数
- ans = pow(2, n, mod) - 2 * sum(dp[n])
- 要记得mod即可
ac code
class Solution:
def countPartitions(self, nums: List[int], k: int) -> int:
if sum(nums) < 2 * k:
return 0
MOD = 10 ** 9 + 7
# 好分组大于等于k没有限制,不好整
# 坏分组只要小于k即可
# f[i][j]表示前i个和为j的个数(都是坏数组,因为j范围是[0, k - 1])
n = len(nums)
f = [[0] * k for _ in range(n + 1)]
# 空组
f[0][0] = 1 # 前0个和为0的总有一个吧,就是啥也没有的那个
for i in range(1, n + 1):
for j in range(k):
f[i][j] = f[i - 1][j] # 不选第i个
if j - nums[i - 1] >= 0:
f[i][j] += f[i - 1][j - nums[i - 1]] # 选第i个
# 行吧,到最后,组还能为空!
# 长见识了!
bads = (2 * sum(f[n])) % MOD # 由于对称性,所以乘2
goods = pow(2, n, MOD) - bads
return goods % MOD
总结
- 再一次吃了dp的亏
- 做的时候知道dp,也知道平方
- 一直在卡一维的定义,没想到转移需要用到之前的和
- 所以是状态定义不熟悉
- 其次是未能想到转换,不过如果想到dp的定义也必须要进行转换才行
- 说到底,dp定义需要加强!