符号运算(华为OD)

输入描述

字符串格式的表达式,仅支持±*/,数字可能超过两位,可能带有空格,没有负数。长度小于200个字符。

输出描述

表达式结果,以最简格式表达 如果结果为整数,那么直接输出整数 如果结果为分数,那么分子分母不可再约分,可以为假分数,不可表达为带分数 结果可能是负数, 负号放在最前面。

示例1

输入:

1 + 5 * 7 / 8

输出:

43/8

示例2

输入:

1 / (0-5)

输出:

-1/5

示例3

输入:

1 * (3*4/(8-(7+0)))

输出:

12

示例4

输入:

(1+2)*3-4/2

输出:

7

java 复制代码
package com.study.algorithm.huaweiOrOD.huaweiOD202509082334.符号运算;

import java.util.*;

/**
 * @ProjectName: 算法
 * @ClassName: Main
 * @Description: 符号运算
 * @Author: Tony_Yi
 * @Date: 2025/10/4 11:05
 * @Version 1.0
 **/
public class Main {
    public static void main(String[] args) {
        // 1. 创建Scanner对象,用于读取控制台输入
        Scanner scan = new Scanner(System.in);
        // 2. 创建Solution实例(核心计算逻辑的载体)
        Solution solution = new Solution();
        try {
            // 3. 读取输入:去掉所有空格(避免"1 + 2"这类带空格的输入影响解析)
            String res = scan.nextLine().replaceAll(" ", "");
            // 4. 计算并打印结果:字符串转字符数组传给calculate,结果自动调用Fraction的toString()格式化
            System.out.println(solution.calculate(res.toCharArray()));
        } catch (Exception e) {
            // 5. 捕获所有异常(如除零错误、表达式格式错误),输出"Error"
            System.out.println("Error");
        }
    }
}

class Solution {
    // 存储运算符优先级:key=运算符,value=优先级(数字越大优先级越高)
    Map<Character, Integer> map;

    // 构造方法:初始化运算符优先级
    public Solution() {
        this.map = new HashMap<>();
        this.map.put('+', 1);  // 加减优先级1(低)
        this.map.put('-', 1);
        this.map.put('*', 2);  // 乘除优先级2(高)
        this.map.put('/', 2);
    }

    public Fraction calculate(char[] cs) {
        int n = cs.length;  // 表达式长度
        // 双栈初始化:
        Deque<Fraction> nums = new ArrayDeque<>();  // 数字栈(存分数对象)
        Deque<Character> ops = new ArrayDeque<>();  // 运算符栈(存运算符/括号)
        // 关键初始化:添加0到数字栈,处理表达式以负数开头的情况(如"-1+2" → "0-1+2")
        nums.addLast(new Fraction(0, 1));
        // 遍历表达式的每个字符
        for (int i = 0; i < n; i++) {
            char c = cs[i];
            // 情况1:遇到左括号'(' → 直接压入运算符栈(等待右括号触发计算)
            if (c == '(') {
                ops.addLast(c);
            } else if (c == ')') {// 情况2:遇到右括号')' → 计算到最近的左括号为止
                // 循环弹出运算符并计算,直到栈顶是'('
                while (!ops.isEmpty()) {
                    if (ops.peekLast() != '(') {  // 栈顶不是左括号 → 计算
                        calc(nums, ops);
                    } else {  // 遇到左括号 → 弹出左括号(括号内计算完成),退出循环
                        ops.pollLast();
                        break;
                    }
                }
            } else {// 情况3:既不是括号 → 数字或运算符
                // 子情况3.1:当前字符是数字 → 解析连续数字(处理多位数,如"123")
                if (isNumber(c)) {
                    int u = 0;  // 存储解析出的整数
                    int j = i;  // 从当前位置i开始,遍历连续数字
                    while (j < n && isNumber(cs[j])) {
                        // 字符转数字:'0'的ASCII码是48,cs[j]-'0'得到实际数字(如'5'-'0'=5)
                        u = u * 10 + (cs[j++] - '0');
                    }
                    // 整数转分数(分子=u,分母=1),压入数字栈
                    nums.addLast(new Fraction(u, 1));
                    i = j - 1;  // i跳至最后一个数字的位置(避免循环重复处理)
                } else {// 子情况3.2:当前字符是运算符(+、-、*、/)
                    // 关键处理:避免运算符前无数字的情况(如"(+1)"→"(0+1)"、"1+-2"→"1+0-2")
                    if (i > 0 && (cs[i - 1] == '(' || cs[i - 1] == '+' || cs[i - 1] == '-')) {
                        nums.addLast(new Fraction(0, 1));  // 补0到数字栈
                    }
                    // 处理运算符优先级:栈顶运算符优先级≥当前运算符 → 先计算栈顶(先乘除后加减)
                    while (!ops.isEmpty() && ops.peekLast() != '(') {
                        char prevOp = ops.peekLast();  // 栈顶运算符
                        // 栈顶优先级更高或相等 → 计算
                        if (map.get(prevOp) >= map.get(c)) {
                            calc(nums, ops);
                        } else {
                            break;  // 栈顶优先级低,停止计算,当前运算符等待后续处理
                        }
                    }
                    // 当前运算符压入运算符栈
                    ops.addLast(c);
                }
            }
        }
        // 遍历结束后,运算符栈可能还有剩余运算符 → 逐个计算
        while (!ops.isEmpty()) {
            calc(nums, ops);
        }
        // 数字栈的最后一个元素就是最终结果
        return nums.peekLast();
    }

    void calc(Deque<Fraction> nums, Deque<Character> ops) {
        // 防御判断:至少需要2个操作数和1个运算符才能计算
        if (nums.size() < 2 || ops.isEmpty()) {
            return;
        }
        // 弹出操作数:注意顺序!后弹出的是第一个操作数(如"a+b",数字栈是[a,b],弹出b再弹出a)
        Fraction b = nums.pollLast();  // 第二个操作数(如"a-b"中的b)
        Fraction a = nums.pollLast();  // 第一个操作数(如"a-b"中的a)
        char op = ops.pollLast();      // 弹出运算符
        Fraction ans = null;  // 计算结果
        // 根据运算符调用Fraction的对应方法
        if (op == '+') {
            ans = a.add(b);      // 加法
        } else if (op == '-') {
            ans = a.subtract(b); // 减法(a - b,不是b - a)
        } else if (op == '*') {
            ans = a.multiply(b); // 乘法
        } else if (op == '/') {
            ans = a.divide(b);   // 除法
        }
        // 计算结果压回数字栈
        nums.addLast(ans);
    }

    boolean isNumber(char c) {
        return Character.isDigit(c);  // 调用Java内置方法判断
    }
}

class Fraction {
    long fz;  // 分子(用long避免整数溢出,如1e9*1e9超出int范围)
    long fm;  // 分母

    // 构造方法:初始化分子和分母
    Fraction(long fz, long fm) {
        this.fz = fz;
        this.fm = fm;
    }

    Fraction add(Fraction other) {
        long nfz = this.fz * other.fm + other.fz * this.fm;
        long nfm = this.fm * other.fm;
        return new Fraction(nfz, nfm).simplify();
    }

    Fraction subtract(Fraction other) {
        long newFz = this.fz * other.fm - other.fz * this.fm;  // 新分子(a*d - c*b)
        long newFm = this.fm * other.fm;                        // 新分母
        return new Fraction(newFz, newFm).simplify();  // 化简后返回
    }

    Fraction multiply(Fraction other) {
        long newFz = this.fz * other.fz;  // 新分子(a*c)
        long newFm = this.fm * other.fm;  // 新分母(b*d)
        return new Fraction(newFz, newFm).simplify();  // 化简后返回
    }

    Fraction divide(Fraction other) {
        // 除零检查:other的分子为0 → 分数值为0,除法无意义
        if (other.fz == 0) {
            throw new ArithmeticException("Error");  // 抛异常,被Main捕获
        }
        long newFz = this.fz * other.fm;  // 新分子(a*d)
        long newFm = this.fm * other.fz;  // 新分母(b*c)
        return new Fraction(newFz, newFm).simplify();  // 化简后返回
    }

    Fraction simplify() {
        long gcdVal = gcd(this.fz, this.fm);  // 计算分子和分母的最大公约数
        // 分子分母同除以最大公约数,得到最简分数
        return new Fraction(this.fz / gcdVal, this.fm / gcdVal);
    }

    public long gcd(long a, long b) {
        return a % b == 0 ? b : gcd(b, a % b);
    }

    public String toString() {
        if (this.fm == 1) {
            // 分母为1 → 整数,直接输出分子(如3/1→"3")
            return Long.toString(this.fz);
        } else {
            // 分母不为1 → 拼接绝对值(避免"-3/-2"这类冗余符号)
            String base = Math.abs(this.fz) + "/" + Math.abs(this.fm);
            // 判断符号:分子分母异号(乘积<0)→ 加负号(如-3/2或3/-2→"-3/2")
            if (this.fz * this.fm < 0) {
                base = "-" + base;
            }
            return base;
        }
    }
}
相关推荐
柳贯一(逆流河版)5 小时前
Nacos 实战指南:微服务下服务注册与配置管理的完整落地
java·微服务·架构
一叶飘零_sweeeet5 小时前
从轮询到实时推送:将站内消息接口改造为 WebSocket 服务
java·websocket
yinke小琪6 小时前
从秒杀系统崩溃到支撑千万流量:我的Redis分布式锁踩坑实录
java·redis·后端
我登哥MVP6 小时前
Apache Tomcat 详解
java·笔记·tomcat
feifeigo1236 小时前
MATLAB的无线传感器网络(WSN)算法仿真
网络·算法·matlab
SXJR6 小时前
Spring前置准备(八)——ConfigurableApplicationContext和DefaultListableBeanFactory的区别
java·后端·spring
胖咕噜的稞达鸭6 小时前
缝合怪deque如何综合list和vector实现及仿函数模板如何优化priority_queue实现
数据结构·c++·算法·链表·list
IccBoY6 小时前
Java采用easyexcel组件进行excel表格单元格的自动合并
java·开发语言·excel
tt5555555555556 小时前
C++ 经典数组算法题解析与实现教程
开发语言·c++·算法