力扣- -241.为运算表达式设计优先级(分治算法)

文章目录

  • 力扣- -241.为运算表达式设计优先级(分治算法)
    • 一、题目描述
    • 二、分析
    • 三、代码
    • 四、优化

一、题目描述

二、分析

  • 看到这道题的第一感觉肯定是复杂,我要穷举出所有可能的加括号方式,是不是还要考虑括号的合法性?是不是还要考虑计算的优先级?

  • 是的,这些都要考虑,但是不需要我们来考虑。利用分治思想和递归函数,算法会帮我们考虑一切细节,也许这就是算法的魅力吧,哈哈哈。

废话不多说,解决本题的关键有两点:

  • 1、不要思考整体,而是把目光聚焦局部,只看一个运算符。

  • 该问题只要思考每个部分需要做什么,而不要思考整体需要做什么。

  • 说白了,解决递归相关的算法问题,就是一个化整为零的过程,你必须瞄准一个小的突破口,然后把问题拆解,大而化小,利用递归函数来解决。

  • 2、明确递归函数的定义是什么,相信并且利用好函数的定义。

  • 这也是前文经常提到的一个点,因为递归函数要自己调用自己,你必须搞清楚函数到底能干嘛,才能正确进行递归调用。

下面来具体解释下这两个关键点怎么理解。

  • 我们先举个例子,比如我给你输入这样一个算式:
1 + 2 * 3 - 4 * 5
  • 请问,这个算式有几种加括号的方式?请在一秒之内回答我。

  • 估计你回答不出来,因为括号可以嵌套,要穷举出来肯定得费点功夫。

  • 不过呢,嵌套这个事情吧,我们人类来看是很头疼的,但对于算法来说嵌套括号不要太简单,一次递归就可以嵌套一层,一次搞不定大不了多递归几次。

  • 所以,作为写算法的人类,我们只需要思考,如果不让括号嵌套(即只加一层括号),有几种加括号的方式?

  • 还是上面的例子,显然我们有四种加括号方式:

(1) + (2 * 3 - 4 * 5)(1 + 2) * (3 - 4 * 5)(1 + 2 * 3) - (4 * 5)(1 + 2 * 3 - 4) * (5)
  • 发现规律了么?其实就是按照运算符进行分割,给每个运算符的左右两部分加括号,这就是之前说的第一个关键点,不要考虑整体,而是聚焦每个运算符。

  • 现在单独说上面的第三种情况:

(1 + 2 * 3) - (4 * 5)
  • 我们用减号-作为分隔,把原算式分解成两个算式1 + 2 * 3和4 * 5

  • 分治分治,分而治之,这一步就是把原问题进行了「分」,我们现在要开始「治」了。

  • 1 + 2 * 3可以有两种加括号的方式,分别是:

(1) + (2 * 3) = 7(1 + 2) * (3) = 9
  • 或者我们可以写成这种形式:
1 + 2 * 3 = [9, 7]
  • 4 * 5当然只有一种加括号方式,就是4 * 5 = [20]。

  • 然后呢,你能不能通过上述结果推导出(1 + 2 * 3) - (4 * 5)有几种加括号方式,或者说有几种不同的结果?

  • 显然,可以推导出来(1 + 2 * 3) - (4 * 5)有两种结果,分别是:

9 - 20 = -117 - 20 = -13
  • 那你可能要问了,1 + 2 * 3 = [9, 7]的结果是我们自己看出来的,如何让算法计算出来这个结果呢?
vector<int> diffWaysToCompute(string input)
  • 这个函数不就是干这个事儿的吗?这就是我们之前说的第二个关键点,明确函数的定义,相信并且利用这个函数定义。

  • 你甭管这个函数怎么做到的,你相信它能做到,然后用就行了,最后它就真的能做到了。

  • 那么,对于(1 + 2 * 3) - (4 * 5)这个例子,我们的计算逻辑其实就是这段代码:

vector<int> diffWaysToCompute("(1 + 2 * 3) - (4 * 5)") {vector<int> res;/****** 分 ******/vector<int> left = diffWaysToCompute("1 + 2 * 3");vector<int> right = diffWaysToCompute("4 * 5");/****** 治 ******/for (int a : left)for (int b : right)res.emplace_back(a - b);return res;
}
  • 好,现在(1 + 2 * 3) - (4 * 5)这个例子是如何计算的,你应该完全理解了吧,那么回来看我们的原始问题。

  • 原问题1 + 2 * 3 - 4 * 5是不是只有(1 + 2 * 3) - (4 * 5)这一种情况?是不是只能从减号-进行分割?

不是,每个运算符都可以把原问题分割成两个子问题,刚才已经列出了所有可能的分割方式:

(1) + (2 * 3 - 4 * 5)(1 + 2) * (3 - 4 * 5)(1 + 2 * 3) - (4 * 5)(1 + 2 * 3 - 4) * (5)

所以,我们需要穷举上述的每一种情况,可以进一步细化一下解法代码

三、代码

class Solution {public:vector<int> diffWaysToCompute(string input) {if(input.empty()) {return {};}//每个递归的结果vector<int> ret;//循环便利找符号for(size_t i = 0;i < input.size(); ++i) {if(input[i] == '+' || input[i] == '-' || input[i] == '*') {//递归左右vector<int> left = diffWaysToCompute(input.substr(0, i));vector<int> right = diffWaysToCompute(input.substr(i + 1, input.size() - i));//计算结果for(auto &l : left) {for( auto &r : right) {if(input[i] == '+') {ret.emplace_back(l + r);}else if(input[i] == '-') {ret.emplace_back(l - r);}else {ret.emplace_back(l * r);}}}}}//递归出口//代表本次递归下来没找到符号,就是只有数字,直接返回即可if(ret.empty()) {ret.emplace_back(stoi(input));return ret;}return ret;}
};
  • 这段代码应该很好理解了吧,就是扫描输入的算式input,每当遇到运算符就进行分割,递归计算出结果后,根据运算符来合并结果

  • 这就是典型的分治思路,先「分」后「治」,先按照运算符将原问题拆解成多个子问题,然后通过子问题的结果来合成原问题的结果。

  • 当然,一个重点在这段代码:

// base case
// 如果 ret 为空,说明算式是一个数字,没有运算符
if(ret.empty()) {ret.emplace_back(stoi(input));return ret;
}
  • 递归函数必须有个 base case 用来结束递归,其实这段代码就是我们分治算法的 base case,代表着你「分」到什么时候可以开始「治」。

  • 我们是按照运算符进行「分」的,一直这么分下去,什么时候是个头?显然,当算式中不存在运算符的时候就可以结束了。

  • 那为什么以ret.empty()作为判断条件?因为当算式中不存在运算符的时候,就不会触发 if 语句,也就不会给ret中添加任何元素。

四、优化

// 备忘录
std::map<string, vector<int>> memo;vector<int> diffWaysToCompute(string input) {// 避免重复计算if (memo.count(input)) {return memo[input];}/****** 其他都不变 ******//***********************/// 将结果添加进备忘录memo[input] = res;return res;
}

力扣- -241.为运算表达式设计优先级相关推荐

  1. 241. 为运算表达式设计优先级

    241. 为运算表达式设计优先级 给定一个含有数字和运算符的字符串,为表达式添加括号,改变其运算优先级以求出不同的结果.你需要给出所有可能的组合的结果.有效的运算符号包含 +, - 以及 * . 示例 ...

  2. LeetCode 241. 为运算表达式设计优先级(动态规划)

    1. 解题 给定一个含有数字和运算符的字符串,为表达式添加括号,改变其运算优先级以求出不同的结果. 你需要给出所有可能的组合的结果.有效的运算符号包含 +, - 以及 * . 示例 1: 输入: &q ...

  3. [leetcode]241. 为运算表达式设计优先级

    1.提交的代码,分治 class Solution {map< pair<int,int>, vector<int> >hash; // 备忘录 key: < ...

  4. leetcode 241. Different Ways to Add Parentheses | 241. 为运算表达式设计优先级(Java)

    题目 https://leetcode.com/problems/different-ways-to-add-parentheses/ 题解 参考:C++ Solution [Faster than ...

  5. 力扣刷题心得(设计类题目)

    设计类题目基本考察的是你对现实事物的抽象能力,一般会遇到一些类的设计.字符串切分.集合的使用(list.map.set.stack.deque)等,结束后我会更新一些关于这些集合的常见使用方法和场景. ...

  6. 力扣150. 逆波兰表达式求值(JavaScript)

    var evalRPN = function(tokens) {let arr=[]const map = new Map([["+", (a, b) => a * 1 + ...

  7. 力扣编程题-解法汇总

    一.力扣链接: 题库 - 力扣 (LeetCode) 全球极客挚爱的技术成长平台 备注:以后每个工作日从前往后刷一道题,然后再加一道每日新题.每天两道题. 二.模版: 标题: 力扣解法汇总5-正则表达 ...

  8. c语言符号运算优先级6,c语言运算符号的优先级

    c语言运算符号的优先级 本文来自百度搜索只为查看方便 优先级等级口诀: 圆方括号.箭头一句号, 自增自减非反负.针强地址长度, 乘除,加减,再移位, 小等大等.等等不等, 八位与,七位异,六位或,五与 ...

  9. 力扣第303场周赛补题

    力扣 第三题:设计食物评分系统 示例 输入 ["FoodRatings", "highestRated", "highestRated", ...

最新文章

  1. 线程组名称_Netty在Dubbo中的线程名称
  2. TransactionTemplate和@Transactional注解的区别
  3. mysql jdbc 占位符_JDBC中占位符报错是什么鬼啊
  4. 洛谷p2234/BZOJ1588 [HNOI2002]营业额统计
  5. CentOS_7 安装MySql5.7
  6. NET开发人员应该要知道
  7. python如何复制文件?
  8. 2.11 计算机视觉现状
  9. fetch与XHR的区别与优势
  10. MySQL — 使用命令创建数据库、链接数据库、创建表、查询表数、删除表
  11. Linux进程控制与进程优先级
  12. idea2021设置代码字体大小
  13. gradle下载不下来依赖包_Gradle 下载依赖jar包及源码
  14. Android 百度语音合成手把手教学
  15. 印刷纸张都有哪些类型?
  16. 货捕头API接口,item_search - 根据关键词取商品列表
  17. 利用神经网络预测股票价格走势
  18. 树洞OCR文字识别v1.1.0官方版
  19. 史上最全电子元器件实物外形图+电路符号
  20. 游戏设计15大参考法则

热门文章

  1. 接收udp数据_聊聊UDP、TCP和实现一个简单的JAVA UDP小Demo
  2. android -------- java虚拟机和Dalvik虚拟机
  3. Node.js与Sails~Model数据模型
  4. 用最简单话概括SSH三框架
  5. jQuery Sizzle选择器(一)
  6. RHEL/CENTOS 性能优化
  7. tourist取模模板
  8. CodeForces - 1373E Sum of Digits(贪心)
  9. (转)区间合并pushup函数模板
  10. Linux服务-SSH服务部署