题目的主要信息:

  • 给出四张扑克牌的牌面,将其当成数字,询问能否通过加减乘除得到24,除法是整除,给出算术式
  • 牌面2到10分别对应数字2到10,然后J、Q、K、A分别代表11、12、13、1
  • 遇到大小王joker、JOKER要输出ERROR
  • 运算中数字的顺序不定,但是不能包含括号
  • 如有多个解输出一个即可,如无解输出NONE

方法一:暴力枚举

具体做法:

我们可以用两个哈希表做到字符串到数字的映射,通过哈希表可以将牌面字符串转换成数字,再将数字转换成牌面字符串。

对于输入的字符串先检查有无大小王,没有再转成数字,然后计算。计算的时候先排成最小的递增序,然后使用next_permutation枚举这四个数字的所有可能的顺序情况,对于每个顺序再枚举中间3个运算符的所有情况,然后将数字数组和运算符数组依次进行计算,如果结果等于24,则可以输出,否则继续找。我们也要准备一个bool型flag用于记录是否找到输出了,每个控制循环的地方都要加flag控制,避免输出之后还在继续找导致输出多个解。

alt

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<unordered_map>
using namespace std;

unordered_map<string, int> CardtoNum = {{"A", 1}, {"2", 2}, {"3", 3}, {"4", 4}, {"5", 5}, {"6", 6}, 
{"7", 7}, {"8", 8}, {"9", 9}, {"10", 10}, {"J", 11}, {"Q", 12}, {"K", 13}}; //输入的字符映射到数字
unordered_map<int, string> NumtoCard = {{1, "A"}, {2, "2"}, {3, "3"}, {4, "4"}, {5, "5"}, {6, "6"}, 
{7, "7"}, {8, "8"}, {9, "9"}, {10, "10"}, {11, "J"}, {12, "Q"}, {13, "K"}}; //返回的数字映射到字符
const vector<char> Op = {'+', '-', '*', '/'};
 
int cal(int a, int b, int op){ //运算
    if(op == 0) 
        return a + b;
    else if (op == 1) 
        return a - b;
    else if (op == 2) 
        return a * b;
    else
        return a / b;
}

bool cal24(vector<int>& a, vector<int>& op){ //这个数字顺序和符号顺序下能够达到24
    vector<int> b(a);
    for(int i = 0; i < 3; i++)
        b[i + 1] = cal(b[i], b[i + 1], op[i]);
    if (b[3] == 24) 
        return true;
    else 
        return false;
}
bool check(vector<int>& nums){
    bool flag = false;
    vector<int> op(4);
    sort(nums.begin(), nums.end());
    do {
        for(int i = 0; i < 4 && !flag; i++){ //枚举三个符号的所有情况
            op[0] = i;
            for(int j = 0; j < 4 && !flag; j++) {
                op[1] = j;
                for(int k = 0; k < 4 && !flag; k++) {
                    op[2] = k;
                    if (cal24(nums, op)) { //符合条件就输出
                        for(int m = 0; m < 3; m++)
                            cout << NumtoCard[nums[m]] << Op[op[m]];
                        cout <<  NumtoCard[nums[3]]  << endl;
                        flag = true; //找到了,准备跳出所有循环
                    }
                }
            }
        }
    } while(next_permutation(nums.begin(), nums.end()) && !flag); //枚举所有的数字顺序
    return flag;
}
 
int main() {
    vector<string> s(4);
    cin >> s[0] >> s[1] >> s[2] >> s[3]; //输入4个字符串
    vector<int> nums(4);
    for(int i = 0; i < 4; i++) { 
        if(s[i] == "joker" || s[i] == "JOKER"){ //遇到大小王
            cout << "ERROR" << endl;
            return 0;
        }
        nums[i] = CardtoNum[s[i]]; //字符串转数字
    }
    if(!check(nums)) //找不到
        cout << "NONE" << endl;
    return 0;
}

复杂度分析:

  • 时间复杂度:O(1)O(1),4个数字,3个运算符不管怎么全排列,都是常数时间
  • 空间复杂度:O(1)O(1),无额外空间,都是常数

方法二:枚举+递归

具体做法:

对于数字的顺序依旧需要枚举,但是对于运算符的顺序我们可以使用递归加回溯来解决。

每次的数字顺序数组加入运算以后,每个数字都有可能有4种全部运算符的可能性,我们添加一个后进入递归,继续运算,直到结束,如果运算到最后一个字符的时候,结果等于24,说明找到,否则就是没找到,需要回溯刚刚用掉的运算符和数字,添加下一个运算符和数字。

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<unordered_map>
using namespace std;

unordered_map<string, int> CardtoNum = {{"A", 1}, {"2", 2}, {"3", 3}, {"4", 4}, {"5", 5}, {"6", 6}, 
{"7", 7}, {"8", 8}, {"9", 9}, {"10", 10}, {"J", 11}, {"Q", 12}, {"K", 13}}; //输入的字符映射到数字
unordered_map<int, string> NumtoCard = {{1, "A"}, {2, "2"}, {3, "3"}, {4, "4"}, {5, "5"}, {6, "6"}, 
{7, "7"}, {8, "8"}, {9, "9"}, {10, "10"}, {11, "J"}, {12, "Q"}, {13, "K"}}; //返回的数字映射到字符
const vector<char> Op = {'+', '-', '*', '/'}; //输出时符号映射
 
int cal(int a, int b, int op){ //运算
    if(op == 0) 
        return a + b;
    else if (op == 1) 
        return a - b;
    else if (op == 2) 
        return a * b;
    else
        return a / b;
}
   
bool dfs(const vector<int>& nums, int start, int sum, int op, vector<int>& ops){ //查找这个数字顺序下有无合适的符号可以让结果等于24
    int newSum = cal(sum, nums[start], op); 
    if(start == 3 && newSum == 24) //末尾比较是否到了24
        return true;
    else if (start == 3)
        return false;
    for(int i = 0; i < 4; i++){ //遍历所有情况的符号
        ops.push_back(i); //尝试每个符号
        if (dfs(nums, start + 1, newSum, i, ops)) //递归计算
            return true;
        ops.pop_back(); //回溯
    }
    return false;
}
 
int main() {
    vector<string> s(4);
    cin >> s[0] >> s[1] >> s[2] >> s[3]; //输入4个字符串
    vector<int> nums(4);
    for(int i = 0; i < 4; i++) { 
        if(s[i] == "joker" || s[i] == "JOKER"){ //遇到大小王
            cout << "ERROR" << endl;
            return 0;
        }
        nums[i] = CardtoNum[s[i]]; //字符串转数字
    }
    sort(nums.begin(), nums.end()); //排成递增序
    do {
        vector<int> ops;
        for (int i = 0; i < 4; i++){ //遍历开头四种运算符
            ops.push_back(i);
            if (dfs(nums, 1, nums[0], i, ops)){ //递归计算这个顺序的顺子有无运算符可以完成 
                cout << NumtoCard[nums[0]] << Op[ops[0]]
                     << NumtoCard[nums[1]] << Op[ops[1]]
                     << NumtoCard[nums[2]] << Op[ops[2]]
                     << NumtoCard[nums[3]] << endl;
                return 0;
            }
            ops.pop_back(); //回溯
        }
    } while(next_permutation(nums.begin(), nums.end())); //枚举所有的顺序
    cout << "NONE" << endl;
    return 0;
}

复杂度分析:

  • 时间复杂度:O(1)O(1),4个数字,3个运算符不管怎么全排列和递归,都是常数时间
  • 空间复杂度:O(1)O(1),递归栈空间是常数