[每日一题] 139. 24点游戏(数组、递归、遍历、多方法)

1. 题目来源

链接:24 点游戏
来源:LeetCode

2. 题目说明

你有 4 张写有 1 到 9 数字的牌。你需要判断是否能通过 *,/,+,-,(,) 的运算得到 24。

示例1:

输入: [4, 1, 8, 7]
输出: True
解释: (8-4) * (7-1) = 24

示例2:

输入: [1, 2, 1, 2]
输出: False

注意:

  1. 除法运算符 /表示实数除法,而不是整数除法。例如4 / (1 - 2/3) = 12
  2. 每个运算符对两个数进行运算。特别是我们不能用 - 作为一元运算符。例如,[1, 1, 1, 1]作为输入时,表达式 -1 - 1 - 1 - 1是不允许的。
  3. 你不能将数字连接在一起。例如,输入为 [1, 2, 1, 2] 时,不能写成 12 + 12 。

3. 题目解析

方法一:针对数字递归遍历

遍历所有的情况,那么既然是要遍历所有的情况,就递归吧:

  • 两个数字加减乘除中加法和乘法对于两个数字的前后顺序没有影响,但是减法和除法是有影响的,而且做除法的时候还要另外保证除数不能为零。
  • 要遍历任意两个数字,然后对于这两个数字,尝试各种加减乘除后得到一个新数字,将这个新数字加到原数组中,记得原来的两个数要移除掉
  • 调用递归函数进行计算,可以发现每次调用递归函数后,数组都减少一个数字,那么当减少到只剩一个数字了,就是最后的计算结果
  • 在递归函数开始时判断,如果数组只有一个数字,且为 24,说明可以算出 24,结果 res 赋值为 true 返回。这里的结果res是一个全局的变量,如果已经为true了,就没必要再进行运算了,所以第一行应该是判断结果res,为 true就直接返回了
  • 遍历任意两个数字,分别用 pq来取出,然后进行两者的各种加减乘除的运算,将结果保存进数组临时数组 t,记得要判断除数不为零。然后将原数组nums中的 pq 移除,遍历临时数组t中的数字,将其加入数组 nums,并调用递归函数
  • 记得完成后要移除数字,恢复状态,这是递归解法很重要的一点。最后还要把 pq 再加回原数组nums,这也是还原状态

注意点:

  1. 不要将vector<int>直接传给vector<double>,会发生自动类型转换导致出错
  2. 判定浮点的规则一定需要牢记

在牛客网题库中,也有这个题,但是牛客的测试用例很不严谨,例如浮点数相等判定,除数为0判定,都没有相应的测试用例进行报错,相对这个题而言 LeetCode 测试严格了很多。牛客Link:[编程题]24点游戏算法

参见代码如下:

// 执行用时 :24 ms, 在所有 C++ 提交中击败了46.72%的用户
// 内存消耗 :11 MB, 在所有 C++ 提交中击败了37.59%的用户

class Solution {
public:
    bool judgePoint24(vector<int>& nums) {
        bool res = false;
        double eps = 0.001;
        vector<double> arr(nums.begin(), nums.end());
        helper(arr, eps, res);
        return res;
    }
    
    void helper(vector<double>& nums, double eps, bool& res) {
        if (res) 
            return;
        if (nums.size() == 1) {
            if (abs(nums[0] - 24) < eps) 
                res = true;
            return;
        }
        for (int i = 0; i < nums.size(); ++i) {
            for (int j = 0; j < i; ++j) {
                double p = nums[i], q = nums[j];
                vector<double> t{p + q, p - q, q - p, p * q};
                if (p > eps) 
                    t.push_back(q / p);
                if (q > eps) 
                    t.push_back(p / q);
                nums.erase(nums.begin() + i);
                nums.erase(nums.begin() + j);
                for (double e : t) {
                    nums.push_back(e);
                    helper(nums, eps, res);
                    nums.pop_back();
                }
                nums.insert(nums.begin() + j, q);
                nums.insert(nums.begin() + i, p);
            }
        }
    }
};
方法二:针对符号递归遍历

来自大佬的的一种很不同的递归写法,这里将加减乘除操作符放到了一个数组 ops 中。并且没有用全局变量res,而是让递归函数带有bool型返回值。在递归函数中,还是要先看nums数组的长度,如果为 1 了,说明已经计算完成,直接看结果是否为 0 就行了。然后遍历任意两个数字,注意这里的ij都分别从 0 到了数组长度,而上面解法的j是从 0 到i,这是因为上面解法将p - q, q - p, q / q, q / p都分别列出来了,而这里仅仅是nums[i] - nums[j], nums[i] / nums[j],所以ij要交换位置,但是为了避免加法和乘法的重复计算,可以做个判断,还有别忘记了除数不为零的判断,ij不能相同的判断。建立一个临时数组t,将非ij位置的数字都加入t,然后遍历操作符数组ops,每次取出一个操作符,然后将nums[i]nums[j]的计算结果加入t,调用递归函数,如果递归函数返回true了,那么就直接返回true。否则移除刚加入的结果,还原t的状态

参见代码如下:

// 执行用时 :28 ms, 在所有 C++ 提交中击败了41.39%的用户
// 内存消耗 :10 MB, 在所有 C++ 提交中击败了51.88%的用户

class Solution {
public:
    bool judgePoint24(vector<int>& nums) {
        double eps = 0.001;
        vector<char> ops{'+', '-', '*', '/'};
        vector<double> arr(nums.begin(), nums.end());
        return helper(arr, ops, eps);
    }
    bool helper(vector<double>& nums, vector<char>& ops, double eps) {
        if (nums.size() == 1) 
            return abs(nums[0] - 24) < eps;
        for (int i = 0; i < nums.size(); ++i) {
            for (int j = 0; j < nums.size(); ++j) {
                if (i == j) 
                    continue;
                vector<double> t;
                for (int k = 0; k < nums.size(); ++k) {
                    if (k != i && k != j) t.push_back(nums[k]);
                }
                for (char op : ops) {
                    if ((op == '+' || op == '*') && i > j) 
                        continue;
                    if (op == '/' && nums[j] < eps) 
                        continue;
                    switch(op) {
                        case '+': t.push_back(nums[i] + nums[j]); break;
                        case '-': t.push_back(nums[i] - nums[j]); break;
                        case '*': t.push_back(nums[i] * nums[j]); break;
                        case '/': t.push_back(nums[i] / nums[j]); break;
                    }
                    if (helper(t, ops, eps)) 
                        return true;
                    t.pop_back();
                }
            }
        }
        return false;
    }
};
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值