5
5
6
6
## 题目大意
7
7
8
- 给定一个整数数组 ` nums ` 和一个整数 ` target ` 。数组长度不超过 20 。向数组中每个整数前加 ` + ` 或 ` - ` 。然后串联起来构造成一个表达式。
8
+ ** 描述 ** : 给定一个整数数组 ` nums ` 和一个整数 ` target ` 。数组长度不超过 ` 20 ` 。向数组中每个整数前加 ` + ` 或 ` - ` 。然后串联起来构造成一个表达式。
9
9
10
- 要求:返回通过上述方法构造的、运算结果等于 ` target ` 的不同表达式数目。
10
+ ** 要求** :返回通过上述方法构造的、运算结果等于 ` target ` 的不同表达式数目。
11
+
12
+ ** 说明** :
13
+
14
+ - $1 \le nums.length \le 20$。
15
+ - $0 \le nums[ i] \le 1000$。
16
+ - $0 \le sum(nums[ i] ) \le 1000$。
17
+ - $-1000 \le target \le 1000$。
18
+
19
+ ** 示例** :
20
+
21
+ ``` Python
22
+ 输入:nums = [1 ,1 ,1 ,1 ,1 ], target = 3
23
+ 输出:5
24
+ 解释:一共有 5 种方法让最终目标和为 3 。
25
+ - 1 + 1 + 1 + 1 + 1 = 3
26
+ + 1 - 1 + 1 + 1 + 1 = 3
27
+ + 1 + 1 - 1 + 1 + 1 = 3
28
+ + 1 + 1 + 1 - 1 + 1 = 3
29
+ + 1 + 1 + 1 + 1 - 1 = 3
30
+
31
+
32
+ 输入:nums = [1 ], target = 1
33
+ 输出:1
34
+ ```
11
35
12
36
## 解题思路
13
37
14
- 暴力方法就是使用深度优先搜索对每位数字遍历 ` + ` 、` - ` ,并统计符合要求的表达式数目。但是实际发现超时了。所以采用动态规划的方法来做。
38
+ ### 思路 1:深度优先搜索(超时)
39
+
40
+ 使用深度优先搜索对每位数字进行 ` + ` 或者 ` - ` ,具体步骤如下:
41
+
42
+ 1 . 定义从位置 ` 0 ` 、和为 ` 0 ` 开始,到达数组尾部位置为止,和为 ` target ` 的方案数为 ` dfs(0, 0) ` ,` size ` 。
43
+ 2 . 下面从位置 ` 0 ` 、和为 ` 0 ` 开始,以深度优先搜索遍历每个位置。
44
+ 3 . 如果当前位置 ` i ` 到达最后一个位置 ` size ` :
45
+ 1 . 如果和 ` cur_sum ` 等于目标和 ` target ` ,则返回方案数 ` 1 ` 。
46
+ 2 . 如果和 ` cur_sum ` 不等于目标和 ` target ` ,则返回方案数 ` 0 ` 。
47
+ 4 . 递归搜索 ` i + 1 ` 位置,和为 ` cur_sum - nums[i] ` 的方案数。
48
+ 5 . 递归搜索 ` i + 1 ` 位置,和为 ` cur_sum + nums[i] ` 的方案数。
49
+ 6 . 将 4 ~ 5 两个方案数加起来就是当前位置 ` i ` 、和为 ` cur_sum ` 的方案数,返回该方案数。
50
+ 7 . 最终方案数为 ` dfs(0, 0) ` ,将其作为答案返回即可。
51
+
52
+ ### 思路 1:代码
53
+
54
+ ``` Python
55
+ class Solution :
56
+ def findTargetSumWays (self , nums : List[int ], target : int ) -> int :
57
+ size = len (nums)
58
+
59
+ def dfs (i , cur_sum ):
60
+ if i == size:
61
+ if cur_sum == target:
62
+ return 1
63
+ else :
64
+ return 0
65
+ ans = dfs(i + 1 , cur_sum - nums[i]) + dfs(i + 1 , cur_sum + nums[i])
66
+ return ans
67
+
68
+ return dfs(0 , 0 )
69
+ ```
70
+
71
+ ### 思路 1:复杂度分析
72
+
73
+ - ** 时间复杂度** :$O(2^n)$。其中 $n$ 为数组 $nums$ 的长度。
74
+ - ** 空间复杂度** :$O(n)$。递归调用的栈空间深度不超过 $n$。
75
+
76
+ ### 思路 2:深度优先搜索 + 记忆化搜索
77
+
78
+ 在思路 1 中我们单独使用深度优先搜索对每位数字进行 ` + ` 或者 ` - ` 的方法超时了。所以我们考虑使用记忆化搜索的方式,避免进行重复搜索。
79
+
80
+ 这里我们使用哈希表 ` table ` 记录遍历过的位置 ` i ` 及所得到的的当前和` cur_sum ` 下的方案数,来避免重复搜索。具体步骤如下:
81
+
82
+ 1 . 定义从位置 ` 0 ` 、和为 ` 0 ` 开始,到达数组尾部位置为止,和为 ` target ` 的方案数为 ` dfs(0, 0) ` 。
83
+ 2 . 下面从位置 ` 0 ` 、和为 ` 0 ` 开始,以深度优先搜索遍历每个位置。
84
+ 3 . 如果当前位置 ` i ` 遍历完所有位置:
85
+ 1 . 如果和 ` cur_sum ` 等于目标和 ` target ` ,则返回方案数 ` 1 ` 。
86
+ 2 . 如果和 ` cur_sum ` 不等于目标和 ` target ` ,则返回方案数 ` 0 ` 。
87
+ 4 . 如果当前位置 ` i ` 、和为 ` cur_sum ` 之前记录过(即使用 ` table ` 记录过对应方案数),则返回该方案数。
88
+ 5 . 如果当前位置 ` i ` 、和为 ` cur_sum ` 之前没有记录过,则:
89
+ 1 . 递归搜索 ` i + 1 ` 位置,和为 ` cur_sum - nums[i] ` 的方案数。
90
+ 2 . 递归搜索 ` i + 1 ` 位置,和为 ` cur_sum + nums[i] ` 的方案数。
91
+ 3 . 将上述两个方案数加起来就是当前位置 ` i ` 、和为 ` cur_sum ` 的方案数,将其记录到哈希表 ` table ` 中,并返回该方案数。
92
+ 6 . 最终方案数为 ` dfs(0, 0) ` ,将其作为答案返回即可。
93
+
94
+ ### 思路 2:代码
95
+
96
+ ``` Python
97
+ class Solution :
98
+ def findTargetSumWays (self , nums : List[int ], target : int ) -> int :
99
+ size = len (nums)
100
+ table = dict ()
101
+
102
+ def dfs (i , cur_sum ):
103
+ if i == size:
104
+ if cur_sum == target:
105
+ return 1
106
+ else :
107
+ return 0
108
+
109
+ if (i, cur_sum) in table:
110
+ return table[(i, cur_sum)]
111
+
112
+ cnt = dfs(i + 1 , cur_sum - nums[i]) + dfs(i + 1 , cur_sum + nums[i])
113
+ table[(i, cur_sum)] = cnt
114
+ return cnt
115
+
116
+ return dfs(0 , 0 )
117
+ ```
118
+
119
+ ### 思路 2:复杂度分析
120
+
121
+ - ** 时间复杂度** :$O(2^n)$。其中 $n$ 为数组 $nums$ 的长度。
122
+ - ** 空间复杂度** :$O(n)$。递归调用的栈空间深度不超过 $n$。
123
+
124
+ ### 思路 3:动态规划
15
125
16
126
假设数组中所有元素和为 ` sum ` ,数组中所有符号为 ` + ` 的元素为 ` sum_x ` ,符号为 ` - ` 的元素和为 ` sum_y ` 。则 ` target = sum_x - sum_y ` 。
17
127
18
128
而 ` sum_x + sum_y = sum ` 。根据两个式子可以求出 ` 2 * sum_x = target + sum ` ,即 ` sum_x = (target + sum) / 2 ` 。
19
129
20
130
那么这道题就变成了,如何在数组中找到一个集合,使集合中元素和为 ` (target + sum) / 2 ` 。这就变为了求容量为 ` (target + sum) / 2 ` 的 01 背包问题。
21
131
22
- 动态规划的状态 ` dp[i] ` 表示为:填满容量为 ` i ` 的背包,有 ` dp[i] ` 种方法。
132
+ ###### 1. 定义状态
133
+
134
+ 定义状态 ` dp[i] ` 表示为:填满容量为 ` i ` 的背包,有 ` dp[i] ` 种方法。
135
+
136
+ ###### 2. 状态转移方程
137
+
138
+ 填满容量为 ` i ` 的背包的方法数来源于:
139
+
140
+ 1 . 不使用当前 ` num ` :只使用之前元素填满容量为 ` i ` 的背包的方法数。
141
+ 2 . 使用当前 ` num ` :填满容量 ` i - num ` 的包的方法数,再填入 ` num ` 的方法数。
142
+
143
+ 则动态规划的状态转移方程为:` dp[i] = dp[i] + dp[i - num] ` 。
23
144
24
- 动态规划的状态转移方程为: ` dp[i] = dp[i] + dp[i - num] ` ,意思为填满容量为 ` i ` 的背包的方法数 = 不使用当前 ` num ` ,只使用之前元素填满容量为 ` i ` 的背包的方法数 + 填满容量 ` i - num ` 的包的方法数,再填入 ` num ` 的方法数。
145
+ ###### 3. 初始化
25
146
26
- ## 代码
147
+ 初始状态下,默认填满容量为 ` 0 ` 的背包有 ` 1 ` 种办法。即 ` dp[i] = 1 ` 。
148
+
149
+ ###### 4. 最终结果
150
+
151
+ 根据状态定义,最后输出 ` dp[sise] ` (即填满容量为 ` size ` 的背包,有 ` dp[size] ` 种方法)即可,其中 ` size ` 为数组 ` nums ` 的长度。
152
+
153
+ ### 思路 3:代码
27
154
28
155
``` Python
29
156
class Solution :
@@ -40,3 +167,8 @@ class Solution:
40
167
return dp[size]
41
168
```
42
169
170
+ ### 思路 3:复杂度分析
171
+
172
+ - ** 时间复杂度** :$O(n)$。$n$ 为数组 $nums$ 的长度。
173
+ - ** 空间复杂度** :$O(n)$。
174
+
0 commit comments