程序员的算法趣题:Q02 数列的四则运算(Java版)

题目说明

在各个数字之间插入四则运算的运算符组成算式,
然后计算算式的结果(某些数位之间可以没有运算符,但整个表达式中最少要插入 1 个运算符)。
例如:
1234 -> 1+2×3-4 = 3
9876 -> 9×87+6 = 789
假设这里的条件是,组合算式的计算结果为“将原数字各个数位上的数逆序排列得到的数”,
并且算式的运算按照四则运算的顺序进行(先乘除,后加减)
那么位于 100~999,符合条件的有以下几种情况。
351 -> 3×51 = 153
621 -> 6×21 = 126
886 -> 8×86 = 688
求:位于 1000~9999,满足上述条件的数。

思路

1.罗列出1000~9999之间的每个数字
2.拆分数字,与运算符组合,拼接成表达式
3.计算表达式的值(①中缀表达式=>后缀表达式(逆波兰表示法) ②计算后缀表达式的值
4.与第一步罗列出的数字的逆序数字进行比较,如果相等,则输出结果
*5.因为4位数拆成多个数相-,/得到的结果都不足4位;相+若要得到4位数(xxx+x=xxxx),则千位和百位必然是9,又因所求结果为逆序后的数字,所以个位和十位也是9,即999+9=1008,并不符合要求,所以有效的运算符只有

代码

public static void main(String[] args) {
    String[] op = {"+", "-", "*", "/", ""}; // 作为数字之间的分隔符(可根据思路第5步优化为{"*",""})
    // 变量n依次表示指定范围内的每个数字
    for (int n = 1000; n < 10000; n++) { // 四位数中间有3个位置可以插入运算符或者""(不插入)
        String num = "" + n; // 将数字转为字符串形式,以便通过正则表达式拆分
        if (num.matches("\\d*0+$")) continue; // 以0结尾的四位数,反转后不足4位数,直接跳过,从而减少循环次数。
        Double expected = Double.parseDouble(new StringBuilder(num).reverse().toString()); // 反转后的数字:期望值
        
        // 罗列出3个位置上的运算符(或"")
        for (int i = 0; i < op.length; i++) {
            for (int j = 0; j < op.length; j++) {
                for (int k = 0; k < op.length; k++) {
                    // 拼接出算数表达式
                    String exp = num.charAt(0) + op[i] + num.charAt(1) + op[j] + num.charAt(2) + op[k] + num.charAt(3);
                    Double res = getResult(exp); // 计算表达式的值,自定义的方法getResult(xx)
                    // 与期望值比较,相同则输出结果
                    if (Double.compare(expected, res) == 0) { // java.lang.NullPointerException
                        System.out.println(num + " -> " + res); // 最终结果:5931 -> 1395.0
                    }
                }
            }
        }
    }
}
// 按照四则运算优先级,计算表达式的值,并返回
private static Double getResult(String exp) {
    /* 1.中缀表达式 --> 后缀表达式
         1) 通过正则表达式切分算数表达式
         2) 从左往右遍历中缀表达式的每个元素:数字和运算符
         3) 若是数字,直接存入List
         4) 若是符号,则①判断优先级②出栈③入栈:
              i. 判断当前符号与栈顶符号(最近一次存入的符号)的优先级
             ii. 如果该符号是右括号,或者优先级低于栈顶符号,则栈顶元素依次出栈并存入StringBuilder
            iii. 然后将当前符号入栈
         5) 遍历结束后,出栈所有运算符
    */
    Stack<Double> nums = new Stack<>(); // 存放数字
    Stack<String> op = new Stack<>(); // 存放符号
    List<String> list = new LinkedList<String>(); // 保存数字和+-*/符号

    // 拆分出数字和操作符
    // \d+(\.\d+)?  表示数字部分,小括号里的是小数部分,?表示0~1次
    // (?<!\d)-?    表示数字部分前面的负号;?表示负号可以没有,或者有一个;(?<!\d)表示不能以数字开头,即这个负号的前面不能是数字(否则-就是减号,而不是负号了)
    Pattern p = Pattern.compile("(?<!\\d)-?\\d+(\\.\\d+)?|[+\\-*/()]"); // Java中的正则表达式用\\表示转义字符
    Matcher m = p.matcher(exp);
    while (m.find()) {
        String s = m.group(); // 依次找出每个元素(数字or字符)

        // 操作符
        if (s.matches("[+\\-*/()]")) {
            switch (s) {
                case "(":  // "(" 入栈
                    op.push("(");
                    break;
                case ")":  // ")" 出栈,直到"("
                    String top = null; // 栈顶的运算符
                    while (!(top = op.pop()).equals("(")) {
                        list.add(top);
                    }
                    break;
                default:  // 四则运算符,比较优先级,出栈比当前符号优先级高的符号,然后入栈当前符号
                    while (!op.empty() && opPriority(s) <= opPriority(op.peek())) { // 自定义的方法opPriority(xx)
                        list.add(op.pop()); // 出栈
                    }
                    op.push(s); // 入栈当前运算符
                    break;
            }
        } else { // 数字
            list.add(s);
        }

    }

    // 出栈所有运算符
    while (!op.isEmpty()) {
        list.add(op.pop());
    }

    //------- 至此,后缀表达式的每个部分均已存入list ------

    // 2.计算后缀表达式的值
    Double res = NaN; // not a number; 需要导包import static java.lang.Double.NaN;
    for (String e : list) { // 依次取出list中的每个操作数和运算符
        if (e.matches("[+\\-*/()]")) { // +-*/运算符,取出nums栈顶的两个数字进行运算,然后将结果存入nums
            double next = nums.pop();
            double pre = nums.pop();
            res = calc(pre, next, e); // 自定义的方法calc(xx)
            nums.push(res);
        } else { // 数字,直接存入nums
            nums.push(Double.parseDouble(e));
        }
    }

    return res;
}
// 四则运算
private static double calc(double pre, double next, String op) {
    switch (op) {
        case "+":
            return pre + next;
        case "-":
            return pre - next;
        case "*":
            return pre * next;
        case "/":
            return pre / next; // Java中两个double相除,0为除数不会报错
        default:
            break;
    }
    throw new IllegalArgumentException("不支持的操作符!");
}
// 计算运算符的优先级。此处指定:数字越大,优先级越高
private static int opPriority(String op) {
    if (op == null) return 0;
    switch (op) {
        case "(":
            return 1;
        case "+":
        case "-":
            return 2;
        case "*":
        case "/":
            return 3;
        default:
            throw new IllegalArgumentException("不支持的操作符!");
    }
}

结果

5931

学习网址

【Java实现四则运算表达式求值(逆波兰法)】https://blog.csdn.net/qq_22795957/article/details/105872740

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值