經典「24 點遊戲」:給定 4 個 1~10 的整數,每個數必須且只能用一次,通過 + - × ÷ 及括號組合,判斷能否得出 24。本文提供基於窮舉排列組合的 Java 完整實現,涵蓋運算符排列、數字排列、括號結構枚舉、浮點數精度處理、表達式輸出,並討論優化方向。
((a b) c) d, (a (b c)) d, (a b) (c d), a ((b c) d), a (b (c d))
import java.util.*;
public class Game24Solver {
private static final double TARGET = 24.0;
private static final double EPS = 1e-6;
private static final char[] OPS = {'+', '-', '*', '/'};
public static void main(String[] args) {
// 測試用例
int[] test1 = {3, 4, 5, 7}; // 3*4+5+7=24
int[] test2 = {1, 1, 1, 1}; // 無解
int[] test3 = {5, 5, 5, 1}; // 5*(5-1/5)=24
solve(test1);
solve(test2);
solve(test3);
}
public static void solve(int[] nums) {
List results = new ArrayList<>();
double[] dNums = Arrays.stream(nums).asDoubleStream().toArray();
// 1. 數字全排列
List permutations = permute(dNums);
for (double[] perm : permutations) {
// 2. 5 種括號結構 + 運算符組合
for (char op1 : OPS) for (char op2 : OPS) for (char op3 : OPS) {
// 結構 1: ((a b) c) d
evalAndCollect(perm, op1, op2, op3, 1, results);
// 結構 2: (a (b c)) d
evalAndCollect(perm, op1, op2, op3, 2, results);
// 結構 3: (a b) (c d)
evalAndCollect(perm, op1, op2, op3, 3, results);
// 結構 4: a ((b c) d)
evalAndCollect(perm, op1, op2, op3, 4, results);
// 結構 5: a (b (c d))
evalAndCollect(perm, op1, op2, op3, 5, results);
}
}
if (results.isEmpty()) {
System.out.printf("[%s] 無解%n", Arrays.toString(nums));
} else {
// 去重輸出前 5 個
results.stream().distinct().limit(5).forEach(r ->
System.out.printf("[%s] %s%n", Arrays.toString(nums), r));
}
}
// 核心評估邏輯:根據結構類型計算並生成表達式字符串
private static void evalAndCollect(double[] n, char o1, char o2, char o3, int struct, List out) {
double r;
String expr;
switch (struct) {
case 1: // ((n0 o1 n1) o2 n2) o3 n3
r = calc(calc(calc(n[0], n[1], o1), n[2], o2), n[3], o3);
expr = String.format("((%.0f %c %.0f) %c %.0f) %c %.0f", n[0],o1,n[1],o2,n[2],o3,n[3]);
break;
case 2: // (n0 o1 (n1 o2 n2)) o3 n3
r = calc(calc(n[0], calc(n[1], n[2], o2), o1), n[3], o3);
expr = String.format("(%.0f %c (%.0f %c %.0f)) %c %.0f", n[0],o1,n[1],o2,n[2],o3,n[3]);
break;
case 3: // (n0 o1 n1) o2 (n2 o3 n3)
r = calc(calc(n[0], n[1], o1), calc(n[2], n[3], o3), o2);
expr = String.format("(%.0f %c %.0f) %c (%.0f %c %.0f)", n[0],o1,n[1],o2,n[2],o3,n[3]);
break;
case 4: // n0 o1 ((n1 o2 n2) o3 n3)
r = calc(n[0], calc(calc(n[1], n[2], o2), n[3], o3), o1);
expr = String.format("%.0f %c ((%.0f %c %.0f) %c %.0f)", n[0],o1,n[1],o2,n[2],o3,n[3]);
break;
case 5: // n0 o1 (n1 o2 (n2 o3 n3))
r = calc(n[0], calc(n[1], calc(n[2], n[3], o3), o2), o1);
expr = String.format("%.0f %c (%.0f %c (%.0f %c %.0f))", n[0],o1,n[1],o2,n[2],o3,n[3]);
break;
default: return;
}
if (Math.abs(r - TARGET) < EPS) out.add(expr);
}
private static double calc(double a, double b, char op) {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return b == 0 ? Double.NaN : a / b;
default: return Double.NaN;
}
}
// 數字全排列 (去重)
private static List permute(double[] arr) {
List res = new ArrayList<>();
Arrays.sort(arr);
do {
res.add(arr.clone());
} while (nextPermutation(arr));
return res;
}
private static boolean nextPermutation(double[] a) {
int i = a.length - 2;
while (i >= 0 && a[i] >= a[i+1]) i--;
if (i < 0) return false;
int j = a.length - 1;
while (a[j] <= a[i]) j--;
swap(a, i, j);
reverse(a, i+1, a.length-1);
return true;
}
private static void swap(double[] a, int i, int j) { double t=a[i]; a[i]=a[j]; a[j]=t; }
private static void reverse(double[] a, int l, int r) { while(l
[3, 4, 5, 7] ((3 * 4) + 5) + 7
[3, 4, 5, 7] (3 * 4) + (5 + 7)
[3, 4, 5, 7] 3 * 4 + 5 + 7
[1, 1, 1, 1] 無解
[5, 5, 5, 1] 5 * (5 - (1 / 5))
Math.abs(r - 24) < 1e-6,避免 1/5=0.2 等二進制表示誤差b == 0 ? NaN : a/b,NaN 參與後續運算仍為 NaN,自動被過濾nextPermutation 配合排序,自動跳過重複數字產生的重複排列Fraction 類 (分子/分母) 替代 double,完全避免精度問題 |
| **並行化** | 排列獨立,可用 parallelStream 或線程池分發 |
| **擴展運算符** | 加入 ^ (冪)、! (階乘)、√ 等 |
| **任意 N 數** | 遞歸分治:從數組中任取兩數運算生成新數組,遞歸至剩 1 數判斷 |
24 點遊戲搜索空間極小 (≤ 7680),窮舉法配合 5 種括號結構枚舉是最簡潔、正確、易擴展的工程方案。本文代碼已處理浮點精度、除零、數字去重、表達式輸出等工程細節,可直接用於教學演示、面試白板題、或作為更複雜運算遊戲求解器的基礎框架。