Java程序调试:6数字组合运算逼近目标值异常
实现程序需满足以下要求:
- 输入:第一行输入6个0-1000的数字,第二行输入目标数字
- 规则:仅使用输入数字与
+、-、*、/构造表达式,加减运算优先级高于乘除,不得使用括号;允许不完全除法,结果可为有理数;每个数字仅能使用一次;表达式合法且至少包含一个数字;禁止拼接数字 - 输出:
|目标值-最接近结果值|,结果与精确值误差小于1e-6即视为正确
运行示例
示例1
输入:
1 2 3 4 5 6
21
输出:
0
说明:所有数字相加可精确得到目标值21
示例2
输入:
16 65 83 61 33 64
934
输出:
0.0625
说明:最优表达式为65 + 83 + 33 + 64 / 16 * 61,计算结果与目标值的差为0.0625
当前实现代码
import java.util.*; public class project { private static Set<Character> operations = new HashSet<>(Arrays.asList('+', '-', '*', '/')); private static double minDiff = Double.MAX_VALUE; private static String closestExpr = ""; public static void main(String[] args) { Scanner sc = new Scanner(System.in); double[] numbers = new double[6]; for (int i = 0; i < 6; i++) { numbers[i] = sc.nextDouble(); } double target = sc.nextDouble(); char[] expr = new char[5]; for (int i = 0; i < 5; i++) { for (char operation : operations) { expr[i] = operation; permuteExpression(numbers, expr, 0, target); } } System.out.println(minDiff); } private static void permuteExpression(double[] numbers, char[] expr, int depth, double target) { if (depth == expr.length) { StringBuilder sb = new StringBuilder(); sb.append(numbers[0]); double result = numbers[0]; for (int i = 0; i < expr.length; i++) { sb.append(expr[i]); sb.append(numbers[i + 1]); switch (expr[i]) { case '+': result += numbers[i + 1]; break; case '-': result -= numbers[i + 1]; break; case '*': result *= numbers[i + 1]; break; case '/': result /= numbers[i + 1]; break; } } double diff = Math.abs(result - target); if (diff < minDiff && diff < Math.pow(10, -6)) { minDiff = diff; closestExpr = sb.toString(); } return; } for (char operation : operations) { expr[depth] = operation; permuteExpression(numbers, expr, depth + 1, target); } } }
存在的问题
- 结果精度不足,大量测试用例失败
- 输入数字较大时,输出始终为
1.7976931348623157E308(即Double.MAX_VALUE),例如示例2测试用例中,正确输出应为0.0625,但当前代码输出该最大值
问题分析与解决建议
1. 错误的minDiff更新条件(核心问题)
代码中更新minDiff的条件为:
if (diff < minDiff && diff < Math.pow(10, -6))
该条件错误地过滤了所有误差大于1e-6的结果,但题目要求找到最接近目标值的结果,无论误差大小;1e-6只是判断结果是否「视为正确」的阈值,而非筛选更新的条件。这直接导致示例2这类非高精度匹配的最优解无法被捕获,minDiff始终保持初始的Double.MAX_VALUE。
修正方式:
去掉误差阈值限制,只要当前差值小于当前minDiff就更新:
if (diff < minDiff) { minDiff = diff; closestExpr = sb.toString(); }
2. 未遍历数字的全排列
当前代码固定使用输入的数字顺序,未生成数字的所有排列组合。题目允许数字顺序任意调整(只要每个数字仅用一次),示例2的最优表达式就使用了与输入不同的数字顺序,代码完全遗漏了这类情况,导致大量可能的最优解未被计算。
解决方式:
递归生成输入数字索引的全排列,遍历所有可能的数字顺序:
// 生成数字索引的全排列 private static void generatePermutations(List<Integer> indices, int start, List<List<Integer>> result) { if (start == indices.size()) { result.add(new ArrayList<>(indices)); return; } for (int i = start; i < indices.size(); i++) { Collections.swap(indices, start, i); generatePermutations(indices, start + 1, result); Collections.swap(indices, start, i); } } // 在main方法中调用生成排列并遍历 List<Integer> indices = new ArrayList<>(); for (int i = 0; i < 6; i++) indices.add(i); List<List<Integer>> permutations = new ArrayList<>(); generatePermutations(indices, 0, permutations); for (List<Integer> perm : permutations) { double[] permutedNumbers = new double[6]; for (int i = 0; i < 6; i++) { permutedNumbers[i] = numbers[perm.get(i)]; } char[] expr = new char[5]; permuteExpression(permutedNumbers, expr, 0, target); }
3. 表达式计算逻辑错误(优先级未正确处理)
题目要求「加减运算优先级高于乘除」,但当前代码采用从左到右依次计算的方式,完全忽略优先级规则。例如表达式a * b + c,代码会计算(a*b)+c,但按照题目规则应先算b+c再乘以a。
解决方式:
分两步计算表达式:
- 先遍历表达式,执行所有加减运算,将中间结果与乘除运算符、未参与加减的数字存入列表
- 再遍历列表,执行所有乘除运算得到最终结果
使用BigDecimal保证计算精度:
private static BigDecimal calculateResult(double[] numbers, char[] expr) { List<Object> parts = new ArrayList<>(); parts.add(BigDecimal.valueOf(numbers[0])); for (int i = 0; i < expr.length; i++) { char op = expr[i]; BigDecimal num = BigDecimal.valueOf(numbers[i+1]); if (op == '+' || op == '-') { BigDecimal prev = (BigDecimal) parts.remove(parts.size()-1); parts.add(op == '+' ? prev.add(num) : prev.subtract(num)); } else { parts.add(op); parts.add(num); } } // 计算乘除 BigDecimal result = (BigDecimal) parts.get(0); for (int i = 1; i < parts.size(); i += 2) { char op = (char) parts.get(i); BigDecimal num = (BigDecimal) parts.get(i+1); if (op == '*') { result = result.multiply(num); } else { // 保留20位小数避免精度损失 result = result.divide(num, 20, RoundingMode.HALF_UP); } } return result; }
4. 浮点精度损失
使用double计算会累积浮点误差,导致差值计算不准确。改用BigDecimal进行精确计算,再转换为double计算差值:
BigDecimal result = calculateResult(numbers, expr); double diff = Math.abs(result.subtract(BigDecimal.valueOf(target)).doubleValue());
5. 冗余的运算符遍历逻辑
main方法中提前循环赋值运算符的代码是冗余的,permuteExpression方法已经递归遍历所有运算符组合,直接调用permuteExpression即可。
内容的提问来源于stack exchange,提问作者kollleey

