【题目描述】(百练 1789)
给出 4 个小于 10 个正整数,你可以使用加减乘除 4 种运算以及括号把这 4 个数连接起来得到一个 表达式。现在的问题是,是否存在一种方式使得得到的表达式的结果等于 24。 这里加减乘除以及括号的运算结果和运算的优先级跟我们平常的定义一致(这里的除法定义是实数 除法)。 比如,对于 5,5,5,1,我们知道 5 * (5 – 1 / 5) = 24,因此可以得到 24。又比如,对于 1,1,4, 2,我们怎么都不能得到 24。
【输入格式】
输入数据包括多行,每行给出一组测试数据,包括 4 个小于 10 个正整数。最后一组测试数据中包 括 4 个 0,表示输入的结束,这组数据不用处理。
【输出格式】
对于每一组测试数据,输出一行,如果可以得到 24,输出“YES”;否则,输出“NO”。
【样例输入】
5 5 5 1 |
1 1 4 2 |
0 0 0 0 |
【样例输出】
YES |
NO |
题目分析
看到这个题目,我先想到的解决方案就是穷举。穷举所有的数的排列情况,这需要四个嵌套循环,再穷举所有运算符号,这里值得注意的是四个数之间只需三个运算符号,运算符号需要三个嵌套循环,最后还有括号,尝试所有括号组合来保证运行正确。我们直接上代码:
运行代码
#include <iostream>
#include <algorithm>
#include <cmath> // 用于fabs函数计算浮点数绝对值
using namespace std;
// 计算两个数的运算结果
double calculate(double a, double b, char op) {
switch (op) {
case '+': return a + b; // 加法
case '-': return a - b; // 减法
case '*': return a * b; // 乘法
case '/':
if (b == 0) return 1e9; // 避免除以0,返回一个很大的数
else return a / b; // 除法
}
return 0; // 默认情况下返回0,虽然这里不会执行到
}
// 检查四个数是否能通过加减乘除计算出24
bool check24(double a, double b, double c, double d) {
double nums[] = { a, b, c, d }; // 存储四个数的数组
char ops[] = { '+', '-', '*', '/' }; // 存储四种运算符的数组
// 四重循环枚举四个数的所有排列
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
if (j == i) continue; // 跳过与i相同的j
for (int k = 0; k < 4; ++k) {
if (k == i || k == j) continue; // 跳过与i或j相同的k
for (int l = 0; l < 4; ++l) {
if (l == i || l == j || l == k) continue; // 跳过与i、j或k相同的l
// 三重循环枚举三个运算符的所有组合
for (int m = 0; m < 4; ++m) {
for (int n = 0; n < 4; ++n) {
if (n == m) continue; // 跳过与m相同的n
for (int o = 0; o < 4; ++o) {
if (o == m || o == n) continue; // 跳过与m或n相同的o
// 尝试所有可能的括号组合,并计算表达式的值
double res1 = calculate(calculate(calculate(nums[i], nums[j], ops[m]), nums[k], ops[n]), nums[l], ops[o]);
double res2 = calculate(calculate(nums[i], calculate(nums[j], nums[k], ops[m]), ops[n]), nums[l], ops[o]);
double res3 = calculate(nums[i], calculate(calculate(nums[j], nums[k], ops[m]), nums[l], ops[n]), ops[o]);
double res4 = calculate(nums[i], nums[j], calculate(ops[m], calculate(nums[k], nums[l], ops[n]), ops[o]));
double res5 = calculate(calculate(nums[i], nums[j], ops[m]), calculate(nums[k], nums[l], ops[n]), ops[o]);
// 判断结果是否接近24(由于浮点数运算可能存在误差,使用1e-6作为容差)
if (fabs(res1 - 24) < 1e-6 || fabs(res2 - 24) < 1e-6 ||
fabs(res3 - 24) < 1e-6 || fabs(res4 - 24) < 1e-6 || fabs(res5 - 24) < 1e-6) {
return true; // 找到一种组合使得表达式结果等于24
}
}
}
}
}
}
}
}
return false; // 没有找到任何组合使得表达式结果等于24
}
int main() {
int a, b, c, d;
// 循环读取输入数据,直到遇到四个0为止
while (cin >> a >> b >> c >> d) {
if (a == 0 && b == 0 && c == 0 && d == 0) break; // 输入结束标志
if (check24(a, b, c, d)) {
cout << "YES" << endl;
}
else {
cout << "NO" << endl;
}
}
return 0;
}
运行结果
我们解决了这个问题
我在排列括号组合时鲁莽的认为有10种括号组合,初期也写了10种,体现在代码中表现为res1-res10,但在后面再思考时发现有重复情况,5种便可以表达出所有括号情况,改为了res1-res5,这在算法上毫无疑问的做出了巨大优化。最坏情况的时间复杂度从15360变为了7680。
考虑算法情况得全面,对初稿的重新设计也许会有不一样的结果。
下面的图片来解释我为什么认为括号情况实际只需要考虑5种。