【算法题解】24点运算

327 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第22天,点击查看活动详情

描述

计算24点是一种扑克牌益智游戏,随机抽出4张扑克牌,通过加(+),减(-),乘(*), 除(/)四种运算法则计算得到整数24,本问题中,扑克牌通过如下字符或者字符串表示,其中,小写joker表示小王,大写JOKER表示大王:

3 4 5 6 7 8 9 10 J Q K A 2 joker JOKER

本程序要求实现:输入4张牌,输出一个算式,算式的结果为24点。

详细说明:

1.运算只考虑加减乘除运算,没有阶乘等特殊运算符号,没有括号,友情提醒,整数除法要当心,是属于整除,比如2/3=0,3/2=1;

2.牌面2-10对应的权值为2-10, J、Q、K、A权值分别为为11、12、13、1;

3.输入4张牌为字符串形式,以一个空格隔开,首尾无空格;如果输入的4张牌中包含大小王,则输出字符串“ERROR”,表示无法运算;

4.输出的算式格式为4张牌通过+-*/ 四个运算符相连,中间无空格,4张牌出现顺序任意,只要结果正确;

5.输出算式的运算顺序从左至右,不包含括号,如1+2+34的结果为24,2 A 9 A不能变为(2+1)(9-1)=24

6.如果存在多种算式都能计算得出24,只需输出一种即可,如果无法得出24,则输出“NONE”表示无解。

7.因为都是扑克牌,不存在单个牌为0的情况,且没有括号运算,除数(即分母)的数字不可能为0

数据范围:一行由4张牌组成的字符串

输入描述:

输入4张牌为字符串形式,以一个空格隔开,首尾无空格;

输出描述:

输出怎么运算得到24,如果无法得出24,则输出“NONE”表示无解,如果输入的4张牌中包含大小王,则输出字符串“ERROR”,表示无法运算;

示例1

输入:

A A A A

输出:

NONE

说明:

不能实现           

示例2

输入:

4 2 K A

输出:

K-A*4/2

说明:

 A+K*2-4也是一种答案,输出任意一种即可           

示例3

输入:

B 5 joker 4

输出:

ERROR

说明:

 存在joker,输出ERROR          

示例4

输入:

K Q 6 K

输出:

NONE

说明: 按一般的计算规则来看,K+K-(Q/6)=24 或 K-((Q/6)-K)=24,但是因为这个题目的运算不许有括号,所以去掉括号后变为 K+K-Q/6=26-Q/6=14/6=2 或 K-Q/6-K=1/6-K=0-K=-13,其它情况也不能运算出24点,故不存在,输出NONE

题目的主要信息:

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

方法一:暴力枚举

具体做法:

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

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

#include<bits/stdc++.h>
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<bits/stdc++.h>
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),递归栈空间是常数