LC 282. 给表达式添加运算符
题目描述
这是 LeetCode 上的 282. 给表达式添加运算符 ,难度为 困难。
给定一个仅包含数字 0-9
的字符串 num
和一个目标值整数 target
,在 num
的数字之间添加 二元 运算符(不是一元)+
、-
或 *
,返回所有能够得到目标值的表达式。
示例 1:1
2
3输入: num = "123", target = 6
输出: ["1+2+3", "1*2*3"]
示例 2:1
2
3输入: num = "232", target = 8
输出: ["2*3+2", "2+3*2"]
示例 3:1
2
3输入: num = "105", target = 5
输出: ["1*0+5","10-5"]
示例 4:1
2
3输入: num = "00", target = 0
输出: ["0+0", "0-0", "0*0"]
示例 5:1
2
3输入: num = "3456237490", target = 9191
输出: []
提示:
- 1 <= num.length <= 10
- num 仅含数字
- $-2^{31} <= target <= 2^{31} - 1$
回溯算法
最开始的想法是先使用 DFS
搜出来所有的表达式,然后套用 (题解)227. 基本计算器 II 方案,计算所有表达式的结果,并将计算结果为 $target$ 的表达式加到结果集。
假设原字符串 $num$ 的长度为 $n$,由于每个位置之间存在四种插入决策(不插入符号、+
、-
和 *
),共有 $n - 1$ 个位置需要决策,因此搜索所有表达式的复杂度为 $O(4^{n - 1})$;同时需要对所有的表达式执行计算,复杂度为 $O(n)$,整体复杂度为 $O(n * 4^{n - 1})$。
添加运算符后的表达式长度不会超过 $20$,因此总的计算量应该是在 $10^7$ 以内,但可能是因为常数问题超时了(各种优化双栈操作也还是 TLE,在这上面浪费了好多时间 QWQ)。
因此,我们需要考虑在搜索过程中进行计算,以避免使用 (题解)227. 基本计算器 II 这种常数较大的计算方式。
我们考虑如果只有 +
和 -
的话,可以很容易将运算和回溯搜索所有表达进行结合。但当存在 *
时,由于存在运算优先级的问题,我们需要记录形如 a + b * c
中的乘法部分。
实现上,除了记录当前决策到原串 $num$ 的哪一位 $u$,以及当前的运算结果 $cur$ 以外,还需要额外记录最后一次的计算结果 $prev$,然后在决策表达式中的第 $k$ 个部分时,对本次添加的运算符合做分情况讨论:
- 如果本次添加的
+
操作,且第 $k$ 项的值是 $next$:那么直接使用 $cur + next$ 来更新 $cur$,同时 $next$ 作为下一次的 $prev$; - 如果本次添加的
-
操作,且第 $k$ 项的值是 $next$:同理,那么直接使用 $cur - next$ 来更新 $cur$,同时 $-next$ 作为下一次的 $prev$; - 如果本次添加的
*
操作,且第 $k$ 项的值是 $next$:此时需要考虑运算符的优先级问题,由于本次的 $next$ 是与上一次的操作数 $prev$ 执行乘法,而 $cur$ 已经累加了 $prev$ 的影响,因此需要先减去 $prev$,再加上 $prev next$,以此来更新 $cur$,同时 $prev next$ 也作为下一次的 $prev$。
一些细节:需要注意前导零($0$ 单独作为一位是被允许的,但是多位且首部为 $0$ 是不允许的)以及 +
和 -
不作为一元运算符(运算符不能出现在表达式的首部)的情况。
代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30class Solution {
List<String> ans = new ArrayList<>();
String s;
int n, t;
public List<String> addOperators(String num, int target) {
s = num;
n = s.length();
t = target;
dfs(0, 0, 0, "");
return ans;
}
void dfs(int u, long prev, long cur, String ss) {
if (u == n) {
if (cur == t) ans.add(ss);
return ;
}
for (int i = u; i < n; i++) {
if (i != u && s.charAt(u) == '0') break;
long next = Long.parseLong(s.substring(u, i + 1));
if (u == 0) {
dfs(i + 1, next, next, "" + next);
} else {
dfs(i + 1, next, cur + next, ss + "+" + next);
dfs(i + 1, -next, cur - next, ss + "-" + next);
long x = prev * next;
dfs(i + 1, x, cur - prev + x, ss + "*" + next);
}
}
}
}
- 时间复杂度:$O(4^{n})$
- 空间复杂度:$O(4^{n})$
总结
该做法表面上,只是实现了边搜索边计算的功能,但其本质上是利用了实数集 $S$ 和运算符 +
(-
的本质也是 +
)和 *
能够组成代数系统。
利用代数系统 $(S, +, )$,我们可以确保运算过程中的任意一个中间结果,都能使用形如 `a + b c` 的形式进行表示,因此我们只需要多维护一个后缀串结果即可。
而代数系统的相关知识,以及如何确定能否作为一个代数系统,有兴趣的同学可以尝试去了解一下。
最后
这是我们「刷穿 LeetCode」系列文章的第 No.282
篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!