Skip to content

Commit 782553a

Browse files
committed
282
1 parent 1909ba6 commit 782553a

File tree

2 files changed

+210
-0
lines changed

2 files changed

+210
-0
lines changed

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,5 @@
233233
* [275. H-Index II](leetcode-275-H-IndexII.md)
234234
* [278. First Bad Version](leetcode-278-First-Bad-Version.md)
235235
* [279. Perfect Squares](leetcode-279-Perfect-Squares.md)
236+
* [282. Expression Add Operators](leetcode-282-Expression-Add-Operators.md)
236237
* [更多](more.md)
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# 题目描述(困难难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/282.png)
4+
5+
在数字中间添加加号、减号或者乘号,使得计算结果等于 `target`,返回所有的情况。
6+
7+
# 解法一 回溯法
8+
9+
自己开始想到了回溯法,但思路偏了,只能解决添加加号和减号,乘号不知道怎么处理。讲一下别人的思路,参考 [这里](https://leetcode.com/problems/expression-add-operators/discuss/71895/Java-Standard-Backtrace-AC-Solutoin-short-and-clear)
10+
11+
思路就是添加一个符号,再添加一个数字,计算到目前为止表达式的结果。
12+
13+
当到达字符串末尾的时候,判断当前结果是否等于 `target`
14+
15+
我们先考虑只有加法和减法,举个例子。
16+
17+
![](https://windliang.oss-cn-beijing.aliyuncs.com/282_2.jpg)
18+
19+
选取第一个数字的时候不加符号,然后剩下的都是符号加数字的形式。
20+
21+
如果我们对上边的图做深度优先遍历,会发现我们考虑了所有的表达式,下边就是深度优先遍历的结果。
22+
23+
```java
24+
1 +2 +3 = 6
25+
1 +2 -3 = 0
26+
1 -2 +3 = 2
27+
1 -2 -3 = -4
28+
1 +23 = 24
29+
1 -23 = -22
30+
12 +3 = 15
31+
12 -3 = 9
32+
123 = 123
33+
```
34+
35+
看一下只考虑加法和减法的代码。
36+
37+
有些地方需要注意,我们将字符串转成数字的时候可能会超出 `int` 范围,以及计算目前为止表达式的结果,也就是上图的括号里的值,也可能超出 `int` 范围,所以这两个值我们采用 `long`
38+
39+
还有就是 "01" 这种以 `0` 开头的字符串我们不用考虑,可以直接跳过。
40+
41+
计算上图括号中的值的时候,我们只需要用它的父节点中括号的值,加上或者减去当前值。
42+
43+
```java
44+
public List<String> addOperators2(String num, int target) {
45+
List<String> result = new ArrayList<>();
46+
addOperatorsHelper(num, target, result, "", 0, 0, 0);
47+
return result;
48+
}
49+
50+
// path: 目前为止的表达式
51+
// 字符串开始的位置
52+
// eval 目前为止计算的结果
53+
private void addOperatorsHelper(String num, int target, List<String> result, String path, int start, long eval) {
54+
if (start == num.length()) {
55+
if (target == eval) {
56+
result.add(path);
57+
}
58+
return;
59+
60+
}
61+
for (int i = start; i < num.length(); i++) {
62+
// 数字不能以 0 开头
63+
if (num.charAt(start) == '0' && i > start) {
64+
break;
65+
}
66+
long cur = Long.parseLong(num.substring(start, i + 1));
67+
// 选取第一个数不加符号
68+
if (start == 0) {
69+
addOperatorsHelper(num, target, result, path + cur, i + 1, cur, cur);
70+
} else {
71+
// 加当前值
72+
addOperatorsHelper(num, target, result, path + "+" + cur, i + 1, eval + cur, cur);
73+
// 减当前值
74+
addOperatorsHelper(num, target, result, path + "-" + cur, i + 1, eval - cur, -cur);
75+
}
76+
}
77+
}
78+
```
79+
80+
现在考虑乘法有什么不同。
81+
82+
还是以 `123` 为例。如果还是按照上边的代码的逻辑,如果添加乘法的话就是下边的样子。
83+
84+
```java
85+
1
86+
/
87+
+2(3)
88+
/
89+
*3(9)
90+
```
91+
92+
也就是 `1 +2 *3 = 9`,很明显是错的,原因就在于乘法的优先级较高,并不能直接将前边的结果乘上当前的数。而是用当前数乘以前一个操作数。
93+
94+
算的话,我们可以用之前的值(3)减去前一个操作数(2),然后再加上当前数(3)乘以前一个操作数(2)。
95+
96+
即,`3 - 2 + 3 * 2 = 7`
97+
98+
所以代码的话,我们需要添加一个 `pre` 参数,用来记录上一个操作数。
99+
100+
对于加法和乘法,上一个操作数我们根据符号直接返回 `-2``3` 这种就可以了,但对于乘法有些不同。
101+
102+
如果是连乘的情况,比如对于 `2345`,假设之前已经进行到了 `2 +3 *4 (14)`,现在到 `5` 了。
103+
104+
如果我们想增加乘号,计算表达式 `2 +3 *4 *5` 的值。`5` 的前一个操作数是 `*4` ,我们应该记录的值是 `3 * 4 = 12` 。这样的话才能套用上边的讲的公式。
105+
106+
用之前的值(14)减去前一个操作数(12),然后再加上当前数(5)乘以前一个操作数(12)。
107+
108+
即,`14 - 12 + 5 * 12 = 62`。也就是 `2 +3 *4 *5 = 62`
109+
110+
可以结合代码再看一下。
111+
112+
```java
113+
public List<String> addOperators(String num, int target) {
114+
List<String> result = new ArrayList<>();
115+
addOperatorsHelper(num, target, result, "", 0, 0, 0);
116+
return result;
117+
}
118+
119+
private void addOperatorsHelper(String num, int target, List<String> result, String path, int start, long eval, long pre) {
120+
if (start == num.length()) {
121+
if (target == eval) {
122+
result.add(path);
123+
}
124+
return;
125+
126+
}
127+
for (int i = start; i < num.length(); i++) {
128+
// 数字不能以 0 开头
129+
if (num.charAt(start) == '0' && i > start) {
130+
break;
131+
}
132+
long cur = Long.parseLong(num.substring(start, i + 1));
133+
if (start == 0) {
134+
addOperatorsHelper(num, target, result, path + cur, i + 1, cur, cur);
135+
} else {
136+
// 加当前值
137+
addOperatorsHelper(num, target, result, path + "+" + cur, i + 1, eval + cur, cur);
138+
// 减当前值
139+
addOperatorsHelper(num, target, result, path + "-" + cur, i + 1, eval - cur, -cur);
140+
141+
//乘法有两点不同
142+
143+
//当前表达式的值就是 先减去之前的值,然后加上 当前值和前边的操作数相乘
144+
//eval - pre + pre * cur
145+
146+
//另外 addOperatorsHelper 函数传进 pre 参数需要是 pre * cur
147+
//比如前边讲的 2+ 3 * 4 * 5 这种连乘的情况
148+
addOperatorsHelper(num, target, result, path + "*" + cur, i + 1, eval - pre + pre * cur, pre * cur);
149+
}
150+
}
151+
}
152+
```
153+
154+
上边的我们 `path` 参数采用的是 `String` 类型,`String` 类型进行相加的话会生成新的对象,比较慢。
155+
156+
我们可以用 `StringBuilder` 类型。如果用 `StringBuilder` 的话,每次调用完函数就需要将之前添加的东西删除掉,然后再调用新的函数。
157+
158+
[评论区](https://leetcode.com/problems/expression-add-operators/discuss/71895/Java-Standard-Backtrace-AC-Solutoin-short-and-clear) 介绍了 `StringBuilder` 删除之前添加的元素的方法,可以通过设置 `StringBuilder` 的长度。
159+
160+
```java
161+
public List<String> addOperators(String num, int target) {
162+
List<String> result = new ArrayList<>();
163+
addOperatorsHelper(num, target, result, new StringBuilder(), 0, 0, 0);
164+
return result;
165+
}
166+
167+
private void addOperatorsHelper(String num, int target, List<String> result, StringBuilder path, int start, long eval, long pre) {
168+
if (start == num.length()) {
169+
if (target == eval) {
170+
result.add(path.toString());
171+
}
172+
return;
173+
174+
}
175+
for (int i = start; i < num.length(); i++) {
176+
// 数字不能以 0 开头
177+
if (num.charAt(start) == '0' && i > start) {
178+
break;
179+
}
180+
long cur = Long.parseLong(num.substring(start, i + 1));
181+
int len = path.length();
182+
if (start == 0) {
183+
addOperatorsHelper(num, target, result, path.append(cur), i + 1, cur, cur);
184+
path.setLength(len);
185+
} else {
186+
187+
// 加当前值
188+
addOperatorsHelper(num, target, result, path.append("+").append(cur), i + 1, eval + cur, cur);
189+
path.setLength(len);
190+
// 减当前值
191+
addOperatorsHelper(num, target, result, path.append("-").append(cur), i + 1, eval - cur, -cur);
192+
path.setLength(len);
193+
// 乘当前值
194+
addOperatorsHelper(num, target, result, path.append("*").append(cur), i + 1, eval - pre + pre * cur,
195+
pre * cur);
196+
path.setLength(len);
197+
}
198+
}
199+
}
200+
```
201+
202+
#
203+
204+
如果思路对了,用回溯法可以很快写出来,乘法的情况需要单独考虑一下。
205+
206+
我开始的思路是加数字再加符号,和上边的解法刚好是反过来了,但我的思路解决不了乘法的问题。
207+
208+
这里继续吸取教训,走到死胡同的时候,试着转变思路。
209+

0 commit comments

Comments
 (0)