如何不使用组合数组实现24点判断算法?
不用组合数组实现24点判断的优雅方案
嘿,我懂你为啥不想用那个硬编码的全排列数组来做24点判断了——手动枚举所有排列和运算表达式不仅容易漏情况,代码还特别臃肿。其实咱们可以用递归分治的思路来实现,完全不需要预定义任何组合数组,还能更全面地覆盖所有可能的运算和括号场景。
核心思路:递归分治拆解问题
24点的本质是通过任意组合数字顺序、运算符号和括号,让最终结果等于24。递归分治的思路就是把这个问题逐步拆解:
- 从4个数开始,每次选两个数进行所有可能的运算(加、减、乘、除,注意减法和除法有顺序区别);
- 把运算结果和剩下的数合并成新的数字列表,把问题转化为「用3个数凑24」;
- 重复这个过程,直到列表只剩1个数,判断它是否接近24(因为除法会产生浮点数,要考虑精度误差)。
这个方法自动覆盖了所有数字排列和括号优先级的情况——不同的数对选择顺序,就等价于不同的括号组合。
完整Java实现代码
import java.util.ArrayList; import java.util.List; public class TwentyFourGameSolver { private static final double TARGET = 24; private static final double EPSILON = 1e-6; // 处理浮点数精度误差,避免因计算精度误判 public static boolean canBeEqualTo24(int[] nums) { // 输入合法性校验 if (nums.length != 4) return false; for (int num : nums) { if (num < 1 || num > 9) return false; } // 将int数组转为double列表,方便处理除法运算 List<Double> numList = new ArrayList<>(); for (int num : nums) { numList.add((double) num); } return solve(numList); } private static boolean solve(List<Double> nums) { int size = nums.size(); // 递归终止条件:只剩一个数,判断是否接近目标值24 if (size == 1) { return Math.abs(nums.get(0) - TARGET) < EPSILON; } // 枚举所有可能的两个数组合 for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { if (i == j) continue; // 跳过同一个数的情况 // 收集除了选中的两个数之外的剩余数字 List<Double> nextNums = new ArrayList<>(); for (int k = 0; k < size; k++) { if (k != i && k != j) { nextNums.add(nums.get(k)); } } double a = nums.get(i); double b = nums.get(j); // 加法:a+b(和b+a结果一致,只需要计算一次) nextNums.add(a + b); if (solve(nextNums)) return true; nextNums.remove(nextNums.size() - 1); // 回溯,尝试下一种运算 // 减法:a - b nextNums.add(a - b); if (solve(nextNums)) return true; nextNums.remove(nextNums.size() - 1); // 减法:b - a(顺序不同结果不同,需要单独计算) nextNums.add(b - a); if (solve(nextNums)) return true; nextNums.remove(nextNums.size() - 1); // 乘法:a*b(和b*a结果一致,只需要计算一次) nextNums.add(a * b); if (solve(nextNums)) return true; nextNums.remove(nextNums.size() - 1); // 除法:a / b,除数不能为0 if (Math.abs(b) > EPSILON) { nextNums.add(a / b); if (solve(nextNums)) return true; nextNums.remove(nextNums.size() - 1); } // 除法:b / a,除数不能为0(顺序不同结果不同,需要单独计算) if (Math.abs(a) > EPSILON) { nextNums.add(b / a); if (solve(nextNums)) return true; nextNums.remove(nextNums.size() - 1); } } } // 所有可能的运算组合都尝试过,无法得到24 return false; } // 测试示例 public static void main(String[] args) { System.out.println(canBeEqualTo24(new int[]{4, 1, 8, 7})); // 输出true,对应(8-4)*(7-1)=24 System.out.println(canBeEqualTo24(new int[]{3, 3, 8, 8})); // 输出true,对应8/(3-8/3)=24 System.out.println(canBeEqualTo24(new int[]{1, 2, 3, 4})); // 输出true,对应1*2*3*4=24 System.out.println(canBeEqualTo24(new int[]{1, 1, 1, 1})); // 输出false } }
这个方案的优势
- 无需组合数组:递归过程自动遍历了所有数字的排列和运算顺序,完全不需要手动定义全排列数组;
- 覆盖所有场景:支持非整除的除法运算,能处理像[3,3,8,8]这种经典的24点问题(原代码会因为只支持整除而漏判);
- 扩展性强:如果以后要支持更多数字(比如5个数凑某个目标值),只需要修改输入校验部分,核心递归逻辑无需改动;
- 代码更简洁:避免了原代码中大量重复的条件判断,可读性和维护性更高。
原代码的局限性
原代码通过手动枚举部分排列和运算表达式,很容易遗漏大量可能的解,比如嵌套括号的运算、非整除的除法等,会导致很多本应返回true的情况被误判为false。递归分治的方法则能彻底解决这个问题。
内容的提问来源于stack exchange,提问作者petya1969
相关产品推荐
相关产品推荐

