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

本文介绍了如何运用分治和递归算法解决计算算式所有可能结果的问题。通过聚焦运算符,将大问题分解为小问题,然后递归求解。在解决过程中,通过建立备忘录避免重复计算,最终得到所有可能的加括号方式及其对应的计算结果。算法逻辑清晰,易于理解,适合对算法和递归感兴趣的读者。
摘要由CSDN通过智能技术生成

在这里插入图片描述
刚做这道题时蒙了,不知道从哪里下手,后来发现,这道题其实就是在穷举所有可能的加括号的方式,并计算出对应的结果。那么,是否需要穷举出所有可能的加括号的方式,是不是还要考虑括号的合法性?还要考虑计算的优先级呢?这些当然要考虑,但利用分治思想和递归函数,算法就会帮助我们考虑一切细节。
废话不多说,解决本题的关键有两点:
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 = -11

7 - 20 = -13

那你可能要问了,1 + 2 * 3 = [9, 7]的结果是我们自己看出来的,如何让算法计算出来这个结果呢?

这个简单啊,再回头看下题目给出的函数签名:

// 定义:计算算式 input 所有可能的运算结果
List diffWaysToCompute(String input);
这个函数不就是干这个事儿的吗?这就是我们之前说的第二个关键点,明确函数的定义,相信并且利用这个函数定义。

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

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

List<Integer> diffWaysToCompute("(1 + 2 * 3) - (4 * 5)") {
    List<Integer> res = new LinkedList<>();
    /****** 分 ******/
    List<Integer> left = diffWaysToCompute("1 + 2 * 3");
    List<Integer> right = diffWaysToCompute("4 * 5");
    /****** 治 ******/
    for (int a : left)
        for (int b : right)
            res.add(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)

所以,我们需要穷举上述的每一种情况,可以进一步细化一下解法代码:
那么按照算法逻辑,按照运算符进行分割,一定存在下面两种分割情况:

(1 + 1) + (1 + 1 + 1)

(1 + 1 + 1) + (1 + 1)

算法会依次递归每一种情况,其实就是冗余计算嘛,所以我们可以对解法代码稍作修改,加一个备忘录来避免这种重复计算:

class Solution {
    // 备忘录
    HashMap<String,List<Integer>> memo = new HashMap<>();
    // 明确函数定义,计算算式 input 所有可能的运算结果
    public List<Integer> diffWaysToCompute(String expression) {
        if(memo.containsKey(expression)){
            return memo.get(expression);
        }
        
        
        // 保存结果
        List<Integer> res = new LinkedList<>();
        for(int i = 0;i<expression.length();i++){
            char c = expression.charAt(i);
            // 扫描算式input中的运算符
            if(c == '-'||c=='*'||c=='+'){
                // 以运算符为中心,分割两个字符串,分别递归运算
                List<Integer> left = diffWaysToCompute(expression.substring(0,i));
                List<Integer> right = diffWaysToCompute(expression.substring(i+1));
                // 治理,通过子问题的结果,合成原问题的结果
                for(int a :left){
                    for(int b: right){
                        if(c == '+'){
                            res.add(a+b);
                        }else if(c=='-'){
                            res.add(a-b);
                        }else if(c=='*'){
                            res.add(a*b);
                        }
                    }
                }
            }
            
        }
        // base case
        // 如果 res 为空,说明算式是一个数字,没有运算符
        if(res.isEmpty()){
            res.add(Integer.parseInt(expression));
        }
        return res;

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值