ACM. HJ67 24点游戏算法 & 679. 24 点游戏

HJ67 24点游戏算法 ●●

679. 24点游戏 ●●●

描述

给定一个长度为4的整数数组 cards 。你有 4 张卡片,每张卡片上都包含一个范围在 [1,9] 的数字。您应该使用运算符 [‘+’, ‘-’, ‘*’, ‘/’] 和括号 ‘(’ 和 ‘)’ 将这些卡片上的数字排列成数学表达式,以获得值24。

你须遵守以下规则:

  • 除法运算符 ‘/’ 表示实数除法,而不是整数除法。
    例如, 4 /(1 - 2 / 3)= 4 /(1 / 3)= 12 。
  • 每个运算都在两个数字之间。特别是,不能使用 “-” 作为一元运算符。
    例如,如果 cards =[1,1,1,1] ,则表达式 “-1 -1 -1 -1” 是 不允许 的。
  • 你不能把数字串在一起
    例如,如果 cards =[1,2,1,2] ,则表达式 “12 + 12” 无效。

如果可以得到这样的表达式,其计算结果为 24 ,则返回 true ,否则返回 false 。

输入描述:

读入4个[1,10]的整数,数字允许重复,测试用例保证无异常数字。

输出描述:

对于每组案例,输出一行表示能否得到24点,能输出true,不能输出false。

示例

输入:
7 2 1 10
输出:
true

题解

1. 回溯

参考LeetCode官方题解:

一共有 4 个数和 3 个运算操作,因此可能性非常有限。一共有多少种可能性呢?

首先从 4 个数字中有序地选出 2 个数字,共有 4×3=12 种选法,并选择加、减、乘、除 4 种运算操作之一,用得到的结果取代选出的 2 个数字,剩下 3 个数字。

然后在剩下的 3 个数字中有序地选出 2 个数字,共有 3×2=6 种选法,并选择 4 种运算操作之一,用得到的结果取代选出的 2 个数字,剩下 2 个数字。

最后剩下 2 个数字,有 2 种不同的顺序,并选择 4 种运算操作之一。

因此,一共有 12 × 4 × 6 × 4 × 2 × 4 = 9216 12 \times 4 \times 6 \times 4 \times 2 \times 4=9216 12×4×6×4×2×4=9216 种不同的可能性。

可以通过回溯的方法遍历所有不同的可能性。具体做法是,使用一个列表存储目前的全部数字,每次从列表中选出 2 个数字,再选择一种运算操作,用计算得到的结果取代选出的 2 个数字,这样列表中的数字就减少了 1 个。重复上述步骤,直到列表中只剩下 1 个数字,这个数字就是一种可能性的结果,如果结果等于 24,则说明可以通过运算得到 24。如果所有的可能性的结果都不等于 24,则说明无法通过运算得到 24。

实现时,有一些细节需要注意。

  • 除法运算为实数除法,因此结果为浮点数,列表中存储的数字也都是浮点数。在判断结果是否等于 24 时应考虑精度误差,这道题中,误差小于 1 0 − 6 10^{-6} 106 可以认为是相等。

  • 进行除法运算时,除数不能为 0,如果遇到除数为 0 的情况,则这种可能性可以直接排除。由于列表中存储的数字是浮点数,因此判断除数是否为 0 时应考虑精度误差,这道题中,当一个数字的绝对值小于 1 0 − 6 10^{-6} 106 时,可以认为该数字等于 0。

还有一个可以优化的点。

  • 加法和乘法都满足交换律,因此如果选择的运算操作是加法或乘法,则对于选出的 2 个数字不需要考虑不同的顺序,在遇到第二种顺序时可以不进行运算,直接跳过。

  • 时间复杂度:O(1)。一共有 9216 种可能性,对于每种可能性,各项操作的时间复杂度都是 O(1),因此总时间复杂度是 O(1)。
  • 空间复杂度:O(1)。空间复杂度取决于递归调用层数与存储中间状态的列表,因为一共有 4 个数,所以递归调用的层数最多为 4,存储中间状态的列表最多包含 4 个元素,因此空间复杂度为常数。
#include <iostream>
#include <vector>
using namespace std;

const int TARGET = 24;
const double ERROR = 1e-6;
const int ADD = 0, SUBTRACT = 1, MULTIPLY = 2, DIVIDE = 3;

bool backtrack(vector<double> & nums){
    if(nums.size() == 1) return abs(nums[0] - TARGET) < ERROR;          // 只剩一个数,为最终结果
    int size = nums.size();
    for(int i = 0; i < size; ++i){                                      // 选择不同的两个数作为运算数
        for(int j = 0; j < size; ++j){
            if(i == j) continue;                                        // 避免两个数相同的情况
            vector<double> rest;                                        
            for(int h = 0; h < size; ++h){
                if(h != i && h != j) rest.emplace_back(nums[h]);        // rest用于记录剩下的数,以及两数运算后的结果
            }
            for(int op = 0; op < 4; ++op){                              // 对选取的两个数进行四种运算操作
                if((op == ADD || op == MULTIPLY) && i > j) continue;    // i ADD j == j ADD i,已在 i < j 时计算过,减少重复计算
                if(op == ADD){
                    rest.emplace_back(nums[i] + nums[j]);               // 加法
                }else if(op == SUBTRACT){
                    rest.emplace_back(nums[i] - nums[j]);               // 减法
                }else if(op == MULTIPLY){
                    rest.emplace_back(nums[i] * nums[j]);               // 乘法
                }else{
                    if(abs(nums[j]) < ERROR) continue;                  // 避免0为除数
                    rest.emplace_back(nums[i] / nums[j]);               // 除法
                }
                if(backtrack(rest)) return true;                        // 递归计算剩下的数字,4->3,3->2,2->1
                rest.pop_back();                                        // 回溯,换一个运算符
            }
        }
    }
    return false;
}

int main(){
    vector<double> nums(4, 0);            // 实数运算,将int转换为double,num < 1e-6视为0
    while(cin >> nums[0] >> nums[1] >> nums[2] >> nums[3]){
        if(backtrack(nums)){
            cout << "true" << endl;
        }else{
            cout << "false" << endl;
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值