【图解算法】基本计算器——优美的四则运算设计

求解一个四则运算表达式是我们在学习《数据结构(栈)》时的经典问题。而现在,我们尝试脱离栈的使用,用同样优美的代码实现一个四则运算。
 
下面,将循序渐进,从最简单加减计算器,加上乘除,加上括号,最终呈现一个完整的四则运算计算器。让我们开始吧 >_< !!!

 
 
 

基本计算器1——只有加减

input:“2+3+11-6”
output:10

第一题就涉及到本类问题的一个重要思想:四则运算都可以看成加法运算。

看图很好理解:
在这里插入图片描述

现在思考一个极其重要的问题,上图中分割加减运算为加法运算的依据是什么?换句话讲,当遍历到什么字符时,我们就知道此时应该分割出一个完整加法项了?

是数字吗?当我们遍历到+,就记录一下这个符号;接着遍历到3,便在最终结果上+3。看似是个正确的思路,但是当遍历到1时,程序误认为此次加法分割结束,便在最终结果上+1,可实际上应该+11才对。
 

分割的依据是加号/减号。理解这点后,本题的思路呼之欲出:我们设置一个ops缓存所谓上一个符号,设置一个num记录当前的数字,设置一个res作为最终结果;当c遍历到数字时便记录下来;当c遍历到加号/减号时便将上一个加法项(由opsnum组成)追加到最终结果上;同时c所指向的加号/减号缓存入ops,成为上一个符号,c继续移动。

另外注意,最后一个加法项是不是没被分割出来?因为它的后面没有符号。因此,当遍历到最后一个字符时(len - 1),也作为分割出加法项的依据。

还是看图理解:

在这里插入图片描述

 

下面给出代码(另外,此代码中对于多位数(11)的处理非常优雅,注意体会):

class Solution {
    public int calculate(String s) {
        int num = 0;        // 记录的数字(看看它在上图中的位置)
        char ops = '+';     // 缓存的符号(看看它在上图中的位置)
        int res = 0;        // 最终结果

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // 如果是数字
            if (Character.isDigit(c)) {
                num = num * 10 + c - '0';
            }
            // 如果是加减
            if (c == '+' || c == '-' || i == s.length() - 1) {
                switch (ops) {
                    case '+': res += num; break;
                    case '-': res -= num; break;
                }
                num = 0;
                ops = c;
            }
        }
        return res;
    }
}

 
 
 

基本计算器2——加减乘除

input:“2-3+4*6+2”
output:25

你应该能立马意识到,这比加减运算多出了什么?优先级。

我们暂时不谈优先级,先想想加减乘除运算是否可以分割加法项。不仅可以,从下面的分割中,你更能理解为什么分割的依据是不是数字,不是乘号/除号,而是加号/减号。

还是看图:
在这里插入图片描述

下面解决优先级问题。不难发现,上面的+4不能急着追加到res当中,因此我们还需要一个缓存结果tmp;遍历到加/减/乘/除时,便在tmp运算上一个符号,并缓存当前符号;只有遍历到加号/减号时,才将缓存讲过tmp追加到最终结果res上。

还是看图:
在这里插入图片描述

 
下面给出代码:

class Solution {
    public int calculate(String s) {
        int num = 0;        // 记录的数字
        char ops = '+';     // 缓存的符号
        int tmp = 0;        // 缓存结果
        int res = 0;        // 最终结果

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // 如果是数字
            if (Character.isDigit(c)) {
                num = num * 10 + c - '0';
            }
            // 如果是符号
            if (c == '+' || c == '-' || c == '*' || c == '/' || i == s.length() - 1) {
                switch (ops) {
                    case '+': tmp += num; break;
                    case '-': tmp -= num; break;
                    case '*': tmp *= num; break;
                    case '/': tmp /= num; break;
                }
                if (c == '+' || c == '-' || i == s.length() - 1) {
                    res += tmp;
                    tmp = 0;
                }
                num = 0;
                ops = c;
            }
        }
        return res;
    }
}

 
 
 

基本计算器3——带括号的加减

input:“2-(4-(7+1))”
output:6

这不也是优先级问题吗,括号可以将任何运算上升到最高优先级?

不是。括号可以嵌套。所以这是一个递归问题。
 
不妨暴力一点,直接切割字符串。当遍历到括号时,开启cnt计数用于括号匹配(遇到左括号,计数加一;遇到右括号,计数减一);当cnt回归到0时,将最大括号内部的内容作为一个新的表达式进行递归,递归返回的计算结果赋值给num

依旧是看图:
在这里插入图片描述

 
下面给出代码(在基本计算器1代码基础上,多加了对括号的处理而已):

class Solution {
    public int calculate(String s) {
        int num = 0;        // 记录的数字
        char ops = '+';     // 缓存的符号
        int res = 0;        // 最终结果

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // 如果是括号
            if (c == '(') {
                int cnt = 0;
                int t = i;
                for (; i < s.length(); i++) {
                    if (s.charAt(i) == '(')  cnt++;
                    if (s.charAt(i) == ')')  cnt--;
                    if (cnt == 0) {
                        break;
                    }
                }
                num = calculate(s.substring(t + 1, i));
            }
            // 如果是数字
            if (Character.isDigit(c)) {
                num = num * 10 + c - '0';
            }
            // 如果是加减
            if (c == '+' || c == '-' || i == s.length() - 1) {
                switch (ops) {
                    case '+': res += num; break;
                    case '-': res -= num; break;
                }
                num = 0;
                ops = c;
            }
        }
        return res;
    }
}

 
 
 

基本计算器4——四则运算(带括号的加减乘除)

综合了上面所有的思想:

1) 四则运算可以看成多个加法项

2)分割加法项的依据不是数字,不是乘号/除号,不是括号,而是加号/减号

3)遍历到数字,无脑记录;遍历到符号,计算上一个符号

3)乘除带来的优先级问题用缓存结果解决

4)括号带来的优先级问题用递归字符串解决

以及一些细节让代码更优雅和健壮:

1)最后一个字符也作为切割加法项的标志

2)多位数字的处理

3)表达式中空字符的处理

代码如下:

class Solution {
    public int calculate(String s) {
        int num = 0;        // 当前的数字
        char ops = '+';     // 当前的数字之前的符号
        int tmp = 0;        // 临时结果
        int res = 0;        // 最终结果

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // 如果是括号
            if (c == '(') {
                int cnt = 0;
                int t = i;
                for (; i < s.length(); i++) {
                    if (s.charAt(i) == '(')  cnt++;
                    if (s.charAt(i) == ')')  cnt--;
                    if (cnt == 0) {
                        break;
                    }
                }
                num = calculate(s.substring(t + 1, i));
            }
            // 如果是数字
            if (Character.isDigit(c)) {
                num = num * 10 + c - '0';
            }
            // 如果是符号
            if (c == '+' || c == '-' || c == '*' || c == '/' || i == s.length() - 1) {
                switch (ops) {
                    case '+': tmp += num; break;
                    case '-': tmp -= num; break;
                    case '*': tmp *= num; break;
                    case '/': tmp /= num; break;
                }
                if (c == '+' || c == '-' || i == s.length() - 1) {
                    res += tmp;
                    tmp = 0;
                }
                num = 0;
                ops = c;
            }
        }
        return res;
    }
}

 
 
 
 

 
 
 
 

 
 
 
 

 
 
 
 

L o l i Loli Loli i s is is t h e the the B E S T BEST BEST a n d and and P R E C I O U S PRECIOUS PRECIOUS g i f t gift gift i n in in t h e the the w o r l d . world. world.

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值