想着如何实现一个24点,游戏。然后完成找了,有很多。不过试了几个发现很多答案不是很全,对有些内容支持不是很友好。力扣平台上有一个判断24点是否有解,没有返回24点答案。那么就自己实现一个
1. 24点游戏说明
24点游戏,之前大家都有玩过。就是拿4张扑克,运用加减乘除,该等式等于24.例如[2, 3, 10, 6]4张扑克,那么可以 6*(10-(2*3))=24。
2. 实现原理
计算过程:
(1)从4张牌选出2张,有43种(第一次有4张扑克,可以任意选择一种,选第二张只剩下3张了)。这两张牌可以有4种运算(加减乘除)。那么一共有434
(2)接下来,将前面计算得到得值加入到剩下得牌。那么现在就是剩下3张牌。有32种选择牌,4种运算方式。那么一共有324
(3)再接下来,还剩下两张牌,两个数字有两种不同的顺序,4种运算2*4
(4)剩下一张牌,就是运算公式结果。查看是否等于24即可。
3.代码分析
private List<String> getAns(int[] pokers) {
ArrayList<NumObj> numObjs = new ArrayList<>();
for (int card : pokers) {
numObjs.add(new NumObj(card));
}
ArrayList<String> ans = new ArrayList<>();
dfsJudge(numObjs, ans);
return ans;
}
private void dfsJudge(List<NumObj> numObjs, List<String> ans) {
if (numObjs.size() == 1) {
NumObj curRes = numObjs.get(0);
if (curRes.val > 0 && Math.abs(curRes.val - 24) <= 0.00001) {
ans.add(curRes.cur.substring(1, curRes.cur.length() - 1));
}
return;
}
for (int i = 0; i < numObjs.size(); i++) {
for (int j = i + 1; j < numObjs.size(); j++) {
ArrayList<NumObj> copy = new ArrayList<>(numObjs);
NumObj b = copy.remove(j), a = copy.remove(i);
copy.add(performOperation(a, b, "+"));
dfsJudge(copy, ans);
copy.set(copy.size() - 1, performOperation(a, b, "-"));
dfsJudge(copy, ans);
copy.set(copy.size() - 1, performOperation(a, b, "*"));
dfsJudge(copy, ans);
// 除数不能为0
copy.set(copy.size() - 1, performOperation(a, b, "/"));
dfsJudge(copy, ans);
copy.set(copy.size() - 1, performOperation(b, a, "-"));
dfsJudge(copy, ans);
copy.set(copy.size() - 1, performOperation(b, a, "/"));
dfsJudge(copy, ans);
}
}
}
private NumObj performOperation(NumObj a, NumObj b, String operator) {
NumObj res = new NumObj();
switch (operator) {
case "+":
res.val = a.val + b.val;
break;
case "-":
res.val = a.val - b.val;
break;
case "*":
// 没有break语句,程序会继续执行下一个case.也就是会接着执行/,那么会不符合条件
res.val = a.val * b.val;
break;
case "/":
res.val = a.val / b.val;
break;
default:
throw new RuntimeException("无效的操作符");
}
res.cur = "(" + a.cur + operator + b.cur + ")";
return res;
}
class NumObj {
double val;
public String cur;
NumObj() {
}
NumObj(int val) {
this.val = val;
this.cur = String.valueOf(val);
}
}
(1)这边使用double而不是int是方便处理使用到除法运算,不用处理小数。如果使用int 2/3=0,如果是6/(3/(2+10)),那么不好处理。还有除数不能为0,也需要判断。(Java 1/0和1/0.0结果是不一样小伙伴可以试着执行下结果)
(2)使用NumObj 对象是为了存储前面使用得表达式,方便后面打印。
(3)a+b和b+a效果是一样的,为了减少计算加法和乘法只计算一遍。其实还有 如果a==b那么a-b和b-a效果是一样。
(4)这边留个小疑问,那么一共需要执行多少遍呢,这个实现方式时间复杂度
(5)以上实现方式是参考【详解】递归回溯,考察基本功 | 679. 24点游戏这位大神的求解和求助|如何将24点游戏所有答案都打出来
4.完整代码
1.24点代码
package demo.game;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.util.*;
/**
* @author jisl on 2022/8/10 15:53
* 24点
*/
public class TwentyFourGame {
private final Random random = new Random();
private ExpressionParser parser = new SpelExpressionParser();
private int[] createNum() {
int[] pokers = new int[4];
for (int i = 0; i < pokers.length; i++) {
pokers[i] = random.nextInt(13) + 1;
}
return pokers;
}
private int[] createPokers() {
while (true) {
final int[] pokers = createNum();
if (judgePoint24(pokers)) {
return pokers;
}
}
}
private boolean judgePoint24(int[] nums) {
List<Double> list = new ArrayList<>(4);
for (int num : nums) {
list.add((double) num);
}
return solve(list);
}
boolean solve(List<Double> nums) {
if (nums.size() == 1) {
return Math.abs(nums.get(0) - 24) <= 0.00001;
}
for (int i = 0; i < nums.size(); i++) {
for (int j = i + 1; j < nums.size(); j++) {
List<Double> copy = new ArrayList<>(nums);
double b = copy.remove(j), a = copy.remove(i);
boolean valid = false;
copy.add(a + b);
valid |= solve(copy);
copy.set(copy.size() - 1, a - b);
valid |= solve(copy);
copy.set(copy.size() - 1, a * b);
valid |= solve(copy);
copy.set(copy.size() - 1, a / b);
valid |= solve(copy);
copy.set(copy.size() - 1, b - a);
valid |= solve(copy);
copy.set(copy.size() - 1, b / a);
valid |= solve(copy);
if (valid) {
return true;
}
}
}
return false;
}
/**
* 将所有的方式都计算一遍,符合条件的加入
* 24点计算过程:(1)从4个数字选出2个数字,4*3=12种选择,可以有4种运算。得到的结果是变成1个新的数字
* (2)前面运算得到,那么还有3个数字,3个数字选择两个有 3*2=6种选择,4种运算
* (3)然后变成2个数字,两个数字有两种不同的顺序,4种运算
* 一共有 12*4*6*4*2*4=9216种,实际乘法和加法两个效果一样,那么有 (12*4-12)*(6*4-6)*(2*4-2) 一共有3888种排列组合
*
* @param pokers pokers
* @return java.util.List<java.lang.String>
* @author jisl on 2022/10/30 13:31
**/
private List<String> getAns(int[] pokers) {
ArrayList<NumObj> numObjs = new ArrayList<>();
for (int card : pokers) {
numObjs.add(new NumObj(card));
}
ArrayList<String> ans = new ArrayList<>();
dfsJudge(numObjs, ans);
return ans;
}
private void dfsJudge(List<NumObj> numObjs, List<String> ans) {
if (numObjs.size() == 1) {
NumObj curRes = numObjs.get(0);
if (curRes.val > 0 && Math.abs(curRes.val - 24) <= 0.00001) {
ans.add(curRes.cur.substring(1, curRes.cur.length() - 1));
}
return;
}
for (int i = 0; i < numObjs.size(); i++) {
for (int j = i + 1; j < numObjs.size(); j++) {
ArrayList<NumObj> copy = new ArrayList<>(numObjs);
NumObj b = copy.remove(j), a = copy.remove(i);
copy.add(performOperation(a, b, "+"));
dfsJudge(copy, ans);
copy.set(copy.size() - 1, performOperation(a, b, "-"));
dfsJudge(copy, ans);
copy.set(copy.size() - 1, performOperation(a, b, "*"));
dfsJudge(copy, ans);
// 除数不能为0
copy.set(copy.size() - 1, performOperation(a, b, "/"));
dfsJudge(copy, ans);
copy.set(copy.size() - 1, performOperation(b, a, "-"));
dfsJudge(copy, ans);
copy.set(copy.size() - 1, performOperation(b, a, "/"));
dfsJudge(copy, ans);
}
}
}
private NumObj performOperation(NumObj a, NumObj b, String operator) {
NumObj res = new NumObj();
switch (operator) {
case "+":
res.val = a.val + b.val;
break;
case "-":
res.val = a.val - b.val;
break;
case "*":
// 没有break语句,程序会继续执行下一个case.也就是会接着执行/,那么会不符合条件
res.val = a.val * b.val;
break;
case "/":
res.val = a.val / b.val;
break;
default:
throw new RuntimeException("无效的操作符");
}
res.cur = "(" + a.cur + operator + b.cur + ")";
return res;
}
private boolean isPoint24(String expressionString) {
if ("q".equals(expressionString)) {
return false;
}
try {
// 这边如果要使用/法,需要使用浮点数不然表达式正确会变成不正确。
return Math.abs(MathExpressionCalculator.evaluateExpression(expressionString) - 24) < 0.00001;
} catch (Exception e) {
System.err.println(String.format("异常expressionString:%s,e:%s", expressionString, e.getMessage()));
return false;
}
}
public void start() {
int total = 0;
int score = 0;
System.out.println("游戏开始");
Scanner scanner = new Scanner(System.in);
while (true) {
total += 1;
final int[] pokers = createPokers();
System.out.println("当前的数字:" + Arrays.toString(pokers));
System.out.println("请输入你的答案:");
final String expressionString = scanner.nextLine();
final boolean point24 = isPoint24(expressionString);
if (point24) {
score += 1;
System.out.println("恭喜你答对");
} else {
System.err.println("不好意思,你答错了");
}
System.out.printf("题目数:%s,分数:%s,正确率:%.2f%n", total, score, 1.0 * score / total);
if ("q".equals(expressionString) || !point24) {
final List<String> ans = getAns(pokers);
System.out.println("答案数:" + ans.size());
System.out.println(ans);
}
System.out.println("============================");
}
}
class NumObj {
double val;
public String cur;
NumObj() {
}
NumObj(int val) {
this.val = val;
this.cur = String.valueOf(val);
}
}
public static void main(String[] args) {
final TwentyFourGame twentyFourGame = new TwentyFourGame();
twentyFourGame.start();
}
}
2.运算解析
表达式刚开始考虑使用spring的el表达式,不过它解析后是int类型,那么对于除法会出现错误。这边让chatgpt帮忙实现了一个。
package demo.game;
import java.util.Stack;
public class MathExpressionCalculator {
public static void main(String[] args) {
String expression = "3 + 4 * (2 - 1) / 5"; // 期望结果是 3.8
double result = evaluateExpression(expression);
System.out.println("结果: " + result);
}
public static double evaluateExpression(String expression) {
// 移除空格
expression = expression.replaceAll("\\s", "");
Stack<Double> numbers = new Stack<>();
Stack<Character> operators = new Stack<>();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (Character.isDigit(c)) {
StringBuilder sb = new StringBuilder();
while (i < expression.length() && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
sb.append(expression.charAt(i));
i++;
}
i--; // 回退一步以处理下一个字符
double num = Double.parseDouble(sb.toString());
numbers.push(num);
} else if (c == '(') {
operators.push(c);
} else if (c == ')') {
while (!operators.isEmpty() && operators.peek() != '(') {
performOperation(numbers, operators);
}
operators.pop(); // 弹出 '('
} else if (isOperator(c)) {
while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(c)) {
performOperation(numbers, operators);
}
operators.push(c);
}
}
while (!operators.isEmpty()) {
performOperation(numbers, operators);
}
return numbers.pop();
}
private static boolean isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/';
}
private static int precedence(char operator) {
if (operator == '+' || operator == '-') {
return 1;
} else if (operator == '*' || operator == '/') {
return 2;
}
return 0;
}
private static void performOperation(Stack<Double> numbers, Stack<Character> operators) {
double b = numbers.pop();
double a = numbers.pop();
char operator = operators.pop();
double result = 0;
switch (operator) {
case '+':
result = a + b;
break;
case '-':
result = a - b;
break;
case '*':
result = a * b;
break;
case '/':
if (b != 0) {
result = a / b;
} else {
throw new ArithmeticException("除数不能为零");
}
break;
}
numbers.push(result);
}
}
5.结尾
这是以上的分享,主要实现难点是获取24点答案