原理
暴力枚举所有的情况,运算符号4个,加减乘除 + - * / ,整数数字4个。所需要枚举的次数:
- 数字顺序:4个数的全排列,4! = 24。
- 运算符号:4个数需要3个符号,每个可选4种,43 = 64。
- 加括号方式:((AB)C)D、(AB)(CD)、(A(BC))D、A((BC)D)、A(B(CD)),共5种。
- 枚举次数 24*64*5
实现细节
- 全排列枚举由库函数
next_permutation
来完成枚举 - 运算符号由一个整数(范围0—63)表示,
00、01、10、11
分别表示 +-*/,低2位代表运算符1,中2位代表运算符2,高2位代表运算符3。例如37 = 100101
,表示- - *
- 加括号方式手动分别写出来。
- 由于运算过程中含有除法,所以当运算符为除法 且 除数为 0 时,需额外处理一下。
- 验证是否有解和打印解分开,更灵活。
代码
#include <iostream>
#include <algorithm>
//运算 + - * / , 数量 num_n = 4
const int num_n = 4, max_oper = 64;
// 验证是否有解
bool has_answer(int* arr, int oper_code, int method){
bool no_div_err = true;
auto calc = [&no_div_err](double a, double b, int op) -> double {
if(b == 0 && op == 3){
no_div_err = false;
return 0;
}
return op==0 ? a+b : op==1 ? a-b : op==2 ? a*b : a/b;
};
int o1 = oper_code % 4, o2 = (oper_code >> 2) % 4, o3 = (oper_code >> 4) % 4;
if(method == 1){ // ((AB)C)D
return no_div_err && std::abs(calc(calc(calc(arr[0], arr[1], o1), arr[2], o2), arr[3], o3) - 24) < 1e-6;
}else if(method == 2){ // (AB)(CD)
return no_div_err && std::abs(calc(calc(arr[0], arr[1], o1), calc(arr[2], arr[3], o3), o2) - 24) < 1e-6;
}else if(method == 3){ // (A(BC))D
return no_div_err && std::abs(calc(calc(arr[0], calc(arr[1], arr[2], o2), o1), arr[3], o3) - 24) < 1e-6;
}else if(method == 4){ // A((BC)D)
return no_div_err && std::abs(calc(arr[0], calc(calc(arr[1], arr[2], o2), arr[3], o3), o1) - 24) < 1e-6;
}else if(method == 5){ // A(B(CD))
return no_div_err && std::abs(calc(arr[0], calc(arr[1], calc(arr[2], arr[3], o3), o2), o1) - 24) < 1e-6;
}
return false;
}
// 转换为 答案字符串
std::string answer_to_string(int* arr, int oper_code, int method){
const char* op_str = "+-*/";
std::string a = std::to_string(arr[0]), b = std::to_string(arr[1]), c = std::to_string(arr[2]), d = std::to_string(arr[3]);
char o1 = op_str[oper_code%4], o2 = op_str[(oper_code>>2)%4], o3 = op_str[(oper_code>>4)%4];
if(method == 1){// ((AB)C)D
return "((" + a + o1 + b + ")" + o2 + c + ")" + o3 + d + " = 24";
}else if(method == 2){// (AB)(CD)
return "(" + a + o1 + b + ")" + o2 + "(" + c + o3 + d + ") = 24";
}else if(method == 3){// (A(BC))D
return "(" + a + o1 + "(" + b + o2 + c + "))" + o3 + d + " = 24";
}else if(method == 4){ // A((BC)D)
return a + o1 + "((" + b + o2 + c + ")" + o3 + d + ")" + " = 24";
}else if(method == 5){// A(B(CD))
return a + o1 + "(" + b + o2 + "(" + c + o3 + d + "))" + " = 24";
}
return std::string("error occurred");
}
int main() {
int arr[num_n]; // 4 个数字
outer_loop:
while(true) {
for(int i = 0; i < num_n; i++) {
std::cin >> arr[i];
}
std::sort(arr, arr + num_n);
bool result = false;
do {
for(int meth = 1; meth <= 5; meth++) {
for(int oper = 0; oper < max_oper; oper++) {
result = has_answer(arr, oper, meth);
if(result) {
std::cout << answer_to_string(arr, oper, meth) << std::endl;
//若是打印所有可能答案,注释下方 goto 代码
goto outer_loop; // break multiloop
}
}
}
} while(std::next_permutation(arr, arr + num_n));
if(!result)
std::cout << "No solution" << std::endl;
}
}
测试
每次输入4个数,空格分开。
(date: 2019.2.17【旧】已删除)
(date:2024.8.25【新】)
five years later…
(date:2024.1.15)
递归版的
原理就是遍历每一组的两两组合,组合成一个新数,这样总数量减一,就能按数量递归下去了。
// 24dian.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <string>
using Numbers = std::vector<double>;
using Answers = std::vector<std::string>;
//存储步骤解
Answers answers;
//转为字符串 a + b = c
std::string to_concat(double a, char oper, double b, double c) {
char buffer[100];
std::snprintf(buffer, 100, "%g %c %g = %g", a, oper, b, c);
return std::string(buffer);
}
//递归求解
bool my_solve24(const Numbers& nums, Answers* ans = nullptr) {
int n = nums.size();
//只剩一个数时,直接判断是否等于24
if (n == 1) {
return std::abs(nums[0] - 24) < 1e-6;
}
//第一次,清空答案
if (ans == nullptr) {
ans = &answers;
ans->clear();
}
//尝试两两组合
for (int i = 0;i < n;i++) {
for (int j = i + 1;j < n;j++) {
//拷贝一个不含 nums[i] 和 nums[j] 的新数组
Numbers newArr;
for (int k = 0; k < n; k++) {
if (k == i || k == j)
continue;
newArr.push_back(nums[k]);
}
double a = nums[i], b = nums[j];
//a+b
newArr.push_back(a + b);
if (my_solve24(newArr, ans)) {
ans->push_back(to_concat(a, '+', b, a + b));
return true;
}
newArr.pop_back();
//a-b
newArr.push_back(a - b);
if (my_solve24(newArr, ans)) {
ans->push_back(to_concat(a, '-', b, a - b));
return true;
}
newArr.pop_back();
//b-a
newArr.push_back(b - a);
if (my_solve24(newArr, ans)) {
ans->push_back(to_concat(b, '-', a, b - a));
return true;
}
newArr.pop_back();
//a*b
newArr.push_back(a * b);
if (my_solve24(newArr, ans)) {
ans->push_back(to_concat(a, '*', b, a * b));
return true;
}
newArr.pop_back();
//a/b
if (b != 0) {
newArr.push_back(a / b);
if (my_solve24(newArr, ans)) {
ans->push_back(to_concat(a, '/', b, a / b));
return true;
}
newArr.pop_back();
}
//b/a
if (a != 0) {
newArr.push_back(b / a);
if (my_solve24(newArr, ans)) {
ans->push_back(to_concat(b, '/', a, b / a));
return true;
}
newArr.pop_back();
}
}
}
return false;
}
int main() {
const int n = 4;
while (true) {
Numbers nums;
std::cout << "请输入" << n << "个整数,用空格分隔:" << std::endl;
for (int i = 0; i < n; i++) {
double num;
std::cin >> num;
nums.push_back(num);
}
if (my_solve24(nums)) {
//输出答案
for (auto it = answers.rbegin(); it != answers.rend(); it++) {
std::cout << *it << std::endl;
}
}
else {
std::cout << "无解。" << std::endl;
}
}
return 0;
}