[LeetCode] 241. Different Ways to Add Parentheses 添加括号的不同方式
Given a string of numbers and operators, return all possible results from computing all the different possible ways to group numbers and operators. The valid operators are +
, -
and *
.
Example 1:
Input:"2-1-1"
Output:[0, 2]
Explanation:
((2-1)-1) = 0
(2-(1-1)) = 2
Example 2:
Input:"2*3-4*5"
Output:[-34, -14, -10, -10, 10]
Explanation:
(2*(3-(4*5))) = -34
((2*3)-(4*5)) = -14
((2*(3-4))*5) = -10
(2*((3-4)*5)) = -10
(((2*3)-4)*5) = 10
这道题让给了一个可能含有加减乘的表达式,让我们在任意位置添加括号,求出所有可能表达式的不同值。这道题乍一看感觉还蛮难的,给人的感觉是既要在不同的位置上加括号,又要计算表达式的值,结果一看还是道 Medium 的题,直接尼克杨问号脸?!遇到了难题不要害怕,从最简单的例子开始分析,慢慢的找规律,十有八九就会在分析的过程中灵光一现,找到了破题的方法。这道题貌似默认输入都必须是合法的,虽然题目中没有明确的指出这一点,所以我们也就不必进行 valid 验证了。先从最简单的输入开始,若 input 是空串,那就返回一个空数组。若 input 是一个数字的话,那么括号加与不加其实都没啥区别,因为不存在计算,但是需要将字符串转为整型数,因为返回的是一个整型数组。当然,input 是一个单独的运算符这种情况是不存在的,因为前面说了这道题默认输入的合法的。下面来看若 input 是数字和运算符的时候,比如 "1+1" 这种情况,那么加不加括号也没有任何影响,因为只有一个计算,结果一定是2。再复杂一点的话,比如题目中的例子1,input 是 "2-1-1" 时,就有两种情况了,(2-1)-1 和 2-(1-1),由于括号的不同,得到的结果也不同,但如果我们把括号里的东西当作一个黑箱的话,那么其就变为 ()-1 和 2-(),其最终的结果跟括号内可能得到的值是息息相关的,那么再 general 一点,实际上就可以变成 () ? () 这种形式,两个括号内分别是各自的表达式,最终会分别计算得到两个整型数组,中间的问号表示运算符,可以是加,减,或乘。那么问题就变成了从两个数组中任意选两个数字进行运算,瞬间变成我们会做的题目了有木有?而这种左右两个括号代表的黑盒子就交给递归去计算,像这种分成左右两坨的 pattern 就是大名鼎鼎的分治法 Divide and Conquer 了,是必须要掌握的一个神器。类似的题目还有之前的那道 Unique Binary Search Trees II 用的方法一样,用递归来解,划分左右子树,递归构造。
好,继续来说这道题,我们不用新建递归函数,就用其本身来递归就行,先建立一个结果 res 数组,然后遍历 input 中的字符,根据上面的分析,我们希望在每个运算符的地方,将 input 分成左右两部分,从而扔到递归中去计算,从而可以得到两个整型数组 left 和 right,分别表示作用两部分各自添加不同的括号所能得到的所有不同的值,此时我们只要分别从两个数组中取数字进行当前的运算符计算,然后把结果存到 res 中即可。当然,若最终结果 res 中还是空的,那么只有一种情况,input 本身就是一个数字,直接转为整型存入结果 res 中即可,参见代码如下:
解法一:
class Solution {
public:
vector<int> diffWaysToCompute(string input) {
vector<int> res;
for (int i = ; i < input.size(); ++i) {
if (input[i] == '+' || input[i] == '-' || input[i] == '*') {
vector<int> left = diffWaysToCompute(input.substr(, i));
vector<int> right = diffWaysToCompute(input.substr(i + ));
for (int j = ; j < left.size(); ++j) {
for (int k = ; k < right.size(); ++k) {
if (input[i] == '+') res.push_back(left[j] + right[k]);
else if (input[i] == '-') res.push_back(left[j] - right[k]);
else res.push_back(left[j] * right[k]);
}
}
}
}
if (res.empty()) res.push_back(stoi(input));
return res;
}
};
我们也可以使用 HashMap 来保存已经计算过的情况,这样可以减少重复计算,从而提升运算速度,以空间换时间,岂不美哉,参见代码如下:
解法二:
class Solution {
public:
unordered_map<string, vector<int>> memo;
vector<int> diffWaysToCompute(string input) {
if (memo.count(input)) return memo[input];
vector<int> res;
for (int i = ; i < input.size(); ++i) {
if (input[i] == '+' || input[i] == '-' || input[i] == '*') {
vector<int> left = diffWaysToCompute(input.substr(, i));
vector<int> right = diffWaysToCompute(input.substr(i + ));
for (int j = ; j < left.size(); ++j) {
for (int k = ; k < right.size(); ++k) {
if (input[i] == '+') res.push_back(left[j] + right[k]);
else if (input[i] == '-') res.push_back(left[j] - right[k]);
else res.push_back(left[j] * right[k]);
}
}
}
}
if (res.empty()) res.push_back(stoi(input));
memo[input] = res;
return res;
}
};
当然,这道题还可以用动态规划 Dynamic Programming 来做,但明显没有分治法来的简单,但是既然论坛里这么多陈独秀同学,博主还是要给以足够的尊重的。这里用一个三维数组 dp,其中 dp[i][j] 表示在第i个数字到第j个数字之间范围内的子串添加不同括号所能得到的不同值的整型数组,所以是个三位数组,需要注意的是我们需要对 input 字符串进行预处理,将数字跟操作分开,加到一个字符串数组 ops 中,并统计数字的个数 cnt,用这个 cnt 来初始化 dp 数组的大小,并同时要把 dp[i][j] 的数组中都加上第i个数字,通过 ops[i*2] 取得,当然还需要转为整型数。既然 dp 是个三维数组,那么肯定要用3个 for 循环来更新,这里采用的更新顺序跟之前那道 Burst Balloons 是一样的,都是从小区间往大区间更新,由于小区间之前更新过,所以我们将数字分为两部分 [i, j] 和 [j, i+len],然后分别取出各自的数组 dp[i][j] 和 dp[j][i+len],把对应的运算符也取出来,现在又变成了两个数组中任取两个数字进行运算,又整两个 for 循环,所以总共整了5个 for 循环嵌套,啊呀妈呀,看这整的,看不晕你算我输,参见代码如下:
解法三:
class Solution {
public:
vector<int> diffWaysToCompute(string input) {
if (input.empty()) return {};
vector<string> ops;
int n = input.size();
for (int i = ; i < n; ++i) {
int j = i;
while (j < n && isdigit(input[j])) ++j;
ops.push_back(input.substr(i, j - i));
if (j < n) ops.push_back(input.substr(j, ));
i = j;
}
int cnt = (ops.size() + ) / ;
vector<vector<vector<int>>> dp(cnt, vector<vector<int>>(cnt, vector<int>()));
for (int i = ; i < cnt; ++i) dp[i][i].push_back(stoi(ops[i * ]));
for (int len = ; len < cnt; ++len) {
for (int i = ; i < cnt - len; ++i) {
for (int j = i; j < i + len; ++j) {
vector<int> left = dp[i][j], right = dp[j + ][i + len];
string op = ops[j * + ];
for (int num1 : left) {
for (int num2 : right) {
if (op == "+") dp[i][i + len].push_back(num1 + num2);
else if (op == "-") dp[i][i + len].push_back(num1 - num2);
else dp[i][i + len].push_back(num1 * num2);
}
}
}
}
}
return dp[][cnt - ];
}
};
类似题目:
参考资料:
https://leetcode.com/problems/different-ways-to-add-parentheses/
LeetCode All in One 题目讲解汇总(持续更新中...)
[LeetCode] 241. Different Ways to Add Parentheses 添加括号的不同方式的更多相关文章
- [LeetCode] Different Ways to Add Parentheses 添加括号的不同方式
Given a string of numbers and operators, return all possible results from computing all the differen ...
- LN : leetcode 241 Different Ways to Add Parentheses
lc 241 Different Ways to Add Parentheses 241 Different Ways to Add Parentheses Given a string of num ...
- LeetCode 241. Different Ways to Add Parentheses为运算表达式设计优先级 (C++)
题目: Given a string of numbers and operators, return all possible results from computing all the diff ...
- (medium)LeetCode 241.Different Ways to Add Parentheses
Given a string of numbers and operators, return all possible results from computing all the differen ...
- leetcode@ [241] Different Ways to Add Parentheses (Divide and Conquer)
https://leetcode.com/problems/different-ways-to-add-parentheses/ Given a string of numbers and opera ...
- [LeetCode#241]Different Ways to Add Parentheses
Problem: Given a string of numbers and operators, return all possible results from computing all the ...
- leetcode 96. Unique Binary Search Trees 、95. Unique Binary Search Trees II 、241. Different Ways to Add Parentheses
96. Unique Binary Search Trees https://www.cnblogs.com/grandyang/p/4299608.html 3由dp[1]*dp[1].dp[0]* ...
- 241. Different Ways to Add Parentheses
241. Different Ways to Add Parentheses https://leetcode.com/problems/different-ways-to-add-parenthes ...
- 【LeetCode】241. Different Ways to Add Parentheses
Different Ways to Add Parentheses Given a string of numbers and operators, return all possible resul ...
随机推荐
- sqlplus简单使用
登录 C:\Users\inmeditation>sqlplus 请输入用户名: scott 输入口令: 查看当前行长 SQL> show linesize; linesize 80 查看 ...
- vue-cli安装以及创建一个简单的项目(一)(Node\npm\webpack简单使用)
1.关系介绍 1.简单的说 Node.js 就是运行在服务端的 JavaScript. 2.NPM是随同NodeJS一起安装的包管理工具(新版的nodejs已经集成了npm),能解决NodeJS代码部 ...
- NPOI 帮助类
NPOI 帮助类 代码实现了来自于互联网 using System; using System.Data; using System.IO; using System.Text; using NPOI ...
- U9创建BE组件
打开UBF,新建项目->实体项目 输入名称后,点击确定,第二步:修改名称以在后期作为文件夹区分 第三步:创建实体 第四步:添加U9基础对象引用 拖动到解决方案的Reference 第五步:右键构 ...
- Appium基于PO模型的自动化测试(Python)
基于python单元测试框架unittest完成appium自动化测试,生成基于html可视化测试报告 代码示例: #利用unittest并生成测试报告 class Appium_test(unitt ...
- LeetCode 905. Sort Array By Parity 按奇偶校验排列数组
题目 Given an array A of non-negative integers, return an array consisting of all the even elements of ...
- Python【day 12】生成器和推导式
一.生成器和生成器函数1.生成器和生成器函数的概念 1.生成器的本质是迭代器 2.函数中包含yield,就是生成器函数 2.生成器函数的写法 def func(): a =10 yield 20 ge ...
- RabbitMQ、RPC、SaltStack "贡"具的使用
消息队列 使用队列的场景 在程序系统中,例如外卖系统,订单系统,库存系统,优先级较高 发红包,发邮件,发短信,app消息推送等任务优先级很低,很适合交给消息队列去处理,以便于程序系统更快的处理其他请求 ...
- JavaScript Location 对象用法
Location 对象 Location对象包含有关当前URL的信息.location对象是window对象的一部分,可以通过window.location属性访问. 注意:没有适用于location ...
- Finished with error: ProcessException: Process "D:\FlutterAPP\flutter_appfive\android\gradlew.bat" exited abnormally:
在使用Flutter进行开发是遇到这样一个问题 Finished with error: ProcessException: Process "D:\FlutterAPP\flutter_a ...