494. 目标和
摘要
Title: 494. 目标和
Categories: dp, 背包, dfs
Powered by:NEFU AB-IN
494. 目标和
题意
给你一个非负整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
思路
-
正常dfs
- 需要注意:
- nonlocal cnt ,使用 nonlocal 来引用外部的 cnt,在函数嵌套是用这个。在函数和全局变量时,用global
- 不需要像全排列一样,用个for i in range(n):,去遍历未使用的元素,用了for循环加入未被选的元素,实际上更改了相对位置,其实就相当于把顺序也考虑上了,相当于排列,而不是组合。所以在这里每个元素都有固定的位置,并且每个位置都有两种选择(+ 或 -),不需要使用 for 循环来遍历可能的选择
- 需要注意:
-
dp背包问题(复杂版)
- 定义一个二维数组 dp[i][j],其中 i 表示处理到第 i 个数字,j 表示当前的和,dp[i][j] 表示能够达到和 j 的不同表达式的数目。
- 不能从大到小更新,因为涉及两个数的改变
- 优化为一维数组 dp,其中 dp[j] 表示和为 j 的表达式的个数。(因为dp[i]只和dp[i-1]有关,为滚动数组优化)
- 当前的状态只和前一个状态有关。
- 更新当前状态时,我们可以从后向前遍历来避免覆盖问题。
- 定义一个二维数组 dp[i][j],其中 i 表示处理到第 i 个数字,j 表示当前的和,dp[i][j] 表示能够达到和 j 的不同表达式的数目。
-
dp背包问题(简单版)递推
- 简化问题:找到所有子集,使得这些子集的和等于 (sum(nums) - target) / 2 或者 (sum(nums) + target) / 2,这两个等价
具体步骤如下:
- 计算数组 nums 的总和 sum(nums)。如果 (sum(nums) - target) 不是偶数,那么无法得到整数的目标值,直接返回 0。
- 定义 DP 数组 dp,其中 dp[j] 表示和为 j 的不同表达式的数目。
- 初始化 dp[0] = 1,因为和为 0 的情况是一个合法的表达式。
- 遍历数组 nums,对于每一个数,从背包容量(即目标值)开始,向下遍历,更新 dp 数组。
- 最终 dp[(sum(nums) - target) / 2] 的值就是所求结果。
- 简化问题:找到所有子集,使得这些子集的和等于 (sum(nums) - target) / 2 或者 (sum(nums) + target) / 2,这两个等价
-
dp背包问题(简单版)dfs+记忆化搜索
其实dp递推 = dfs+记忆化搜索,将01背包写成dfs形式,非常直观好理解1
2
3
4
5
6
7
8
9
def dfs(i, c):
if i < 0: # 当i < 0 时说明选完了,当 c = 0 时说明恰好用完,那就是一种方案
return 1 if c == 0 else 0
if c < nums[i]: # 如果体积不够就不选
return dfs(i - 1, c)
return dfs(i - 1, c) + dfs(i - 1, c - nums[i]) # 由 i - 1的两个状态转移而来
return dfs(n - 1, target_sum) -
拓展:
- 如果至多target_sum呢?
1
2
3
4
5
6
7
8
9
def dfs(i, c):
if i < 0:
return 1 # 这里就不用判断了,能递归到终点说明 c>=0,所以能走到这一步的都是答案
if c < nums[i]:
return dfs(i - 1, c)
return dfs(i - 1, c) + dfs(i - 1, c - nums[i])
return dfs(n - 1, target_sum)- 如果至少target_sum呢?
1
2
3
4
5
6
7
8
def dfs(i, c):
if i < 0:
return 1 if c <= 0 else 0 # 这里改成小于等于0,因为c是相对于target_sum的值,而且下面的判断也不需要了,因为那是不让c小于0的判断
return dfs(i - 1, c) + dfs(i - 1, c - nums[i])
return dfs(n - 1, target_sum)
代码
1 | ''' |