You have 4 cards each containing a number from 1 to 9. You need to judge whether they could operated through */+-()to get the value of 24.

Example 1:

Input: [4, 1, 8, 7]
Output: True
Explanation: (8-4) * (7-1) = 24

Example 2:

Input: [1, 2, 1, 2]
Output: False

Note:

  1. The division operator / represents real division, not integer division. For example, 4 / (1 - 2/3) = 12.
  2. Every operation done is between two numbers. In particular, we cannot use - as a unary operator. For example, with [1, 1, 1, 1] as input, the expression -1 - 1 - 1 - 1 is not allowed.
  3. You cannot concatenate numbers together. For example, if the input is [1, 2, 1, 2], we cannot write this as 12 + 12.

这道题就是经典的24点游戏了,记得小时候经常玩这个游戏,就是每个人发四张牌,看谁最快能算出24,这完全是脑力大比拼啊,不是拼的牌技。玩的多了,就会摸出一些套路来,比如尽量去凑2和12,3和8,4和6等等,但是对于一些特殊的case,比如 [1, 5, 5, 5] 这种,正确的解法是 5 * (5 - 1 / 5),一般人都会去试加减乘,和能整除的除法,而像这种带小数的确实很难想到,但是程序计算就没问题,可以遍历所有的情况,这也是这道题的实际意义所在吧。那么既然是要遍历所有的情况,我们应该隐约感觉到应该是要使用递归来做的。我们想,任意的两个数字之间都可能进行加减乘除,其中加法和乘法对于两个数字的前后顺序没有影响,但是减法和除法是有影响的,而且做除法的时候还要另外保证除数不能为零。我们要遍历任意两个数字,然后对于这两个数字,尝试各种加减乘除后得到一个新数字,将这个新数字加到原数组中,记得原来的两个数要移除掉,然后调用递归函数进行计算,我们可以发现每次调用递归函数后,数组都减少一个数字,那么当减少到只剩一个数字了,就是最后的计算结果,所以我们在递归函数开始时判断,如果数组只有一个数字,且为24,说明可以算出24,结果res赋值为true返回。这里我们的结果res是一个全局的变量,如果已经为true了,就没必要再进行运算了,所以第一行应该是判断结果res,为true就直接返回了。我们遍历任意两个数字,分别用p和q来取出,然后进行两者的各种加减乘除的运算,将结果保存进数组临时数组t,记得要判断除数不为零。然后将原数组nums中的p和q移除,遍历临时数组t中的数字,将其加入数组nums,然后调用递归函数,记得完成后要移除数字,恢复状态,这是递归解法很重要的一点。最后还要把p和q再加回原数组nums,这也是还原状态,参见代码如下:

解法一:

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() == ) {
if (abs(nums[] - ) < eps) res = true;
return;
}
for (int i = ; i < nums.size(); ++i) {
for (int j = ; 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 d : t) {
nums.push_back(d);
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就行了。然后遍历任意两个数字,注意这里的i和j都分别从0到了数组长度,而上面解法的j是从0到i,这是因为上面解法将p - q, q - p, q / q, q / p都分别列出来了,而这里仅仅是nums[i] - nums[j], nums[i] / nums[j],所以i和j要交换位置,但是为了避免加法和乘法的重复计算,我们可以做个判断,还有别忘记了除数不为零的判断,i和j不能相同的判断。我们建立一个临时数组t,将非i和j位置的数字都加入t,然后遍历操作符数组ops,每次取出一个操作符,然后将nums[i]和nums[j]的计算结果加入t,调用递归函数,如果递归函数返回true了,那么就直接返回true。否则移除刚加入的结果,还原t的状态,参见代码如下:

解法二:

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() == ) return abs(nums[] - ) < eps;
for (int i = ; i < nums.size(); ++i) {
for (int j = ; j < nums.size(); ++j) {
if (i == j) continue;
vector<double> t;
for (int k = ; 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;
}
};

讨论:博主在调试的时候,遇到了这个test case: [1, 3, 4, 6],返回的是true,但是博主心算了一会,并没有想出其是如何算出24的。所以博主在想,能不能修改下代码,使得其能将运算的过程返回出来。其实并不难改,基于解法二来改一下,我们发现,计算后的结果被存入了临时数组t,进行下一次递归,我们需要将这个过程保存下来,用一个字符串数组,比如"1+3",或者"1-3"等等,这个数组跟数组t大小相同,操作基本相同,同时需要被传入到下一次递归函数中,而在下一次递归函数中,数组t中取出的就是4和-2,但是字符串数组就可以取出"1+3"和"1-3",我们就可以继续和别的数进行计算了,比如要乘以4,我们需要给取出的字符串加上括号,就变成了(1+3)*4了,就通过这种方法就可以将过程返回了,运行test case: [1, 3, 4, 6],返回得到:

(6/(1-(3/4)))

没有问题,还有就是,由于组成24的方法可能不止1种,我们可以将所有情况都返回,那么我们的递归函数就不要有返回值,这样可以遍历完所有的情况,对于test case: [1, 2, 3, 8],返回如下:

((8-2)*(1+3))
(8/(1-(2/3)))
(3/((2-1)/8))
(3*(8/(2-1)))
(8*(3*(2-1)))
(2*(1+(3+8)))
(8/((2-1)/3))
(3*(8*(2-1)))
((1+3)*(8-2))
((3*8)/(2-1))
(8*(3/(2-1)))
((3*8)*(2-1))
(2*(8+(1+3)))
((2-1)*(3*8))
(2*(3+(1+8)))

被惊到了有木有!居然有这么多种计算方法可以得到24~ (修改后的代码贴在了评论区二楼 :)

参考资料:

https://leetcode.com/problems/24-game/

https://leetcode.com/problems/24-game/discuss/107685/679-24-game-c-recursive

https://leetcode.com/problems/24-game/discuss/107673/java-easy-to-understand-backtracking

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] 24 Game 二十四点游戏的更多相关文章

  1. 二十四点算法 java实现

    问题: 给出四个数,不可以重复使用,可以用+ - * /和括号,怎么得出24? 代码: //return -1 表示当前方法不行 private int workByStep(int op,int n ...

  2. HNU 12886 Cracking the Safe 二十四点的判断

    经典的一个题,今天竟然写跪了…… 题意: 给你4个数字,让你判断是否能通过四则运算和括号,凑成24点. 思路: 暴力枚举运算顺序和运算符. 代码: #include <iostream> ...

  3. CCF-CSP题解 201903-2 二十四点

    可枚举. 写栈的主要思想是:一个数栈\(numSta\),一个运算符栈\(opSta\).遇到一个运算符,就把之前优先级\(equal\ or\ greater\ than\)它的运算符处理掉. #i ...

  4. 201903-2 二十四点 Java

    思路: 数据结构中,栈可以解决运算的问题.利用压栈和弹栈操作实现(这里用队列模拟).具体的: 遇到乘除号,弹出栈顶元素,将计算结果压入栈中.遇到加减号,将后面的数一起压入栈中. 注意: substri ...

  5. CCF201903-2二十四点

    思路描述:最开始的思路是拿一个栈来存储数据和符号,在动手实践的过程中发现行不通,单个数字的char和int转换可以,但是加起来的数据两位数字就很难处理了. 然后就去看了看别人的思路,给了我一个很好的启 ...

  6. 第十六次 ccf 201903-2 二十四点

    题意: 计算数学表达式的值, 数学表达式的定义: 4个[0-9]表示数字的字符 ,3个[+-x/]表示运算的字符 可以用正则为: ([0-9][+-x/]){3}[0-9] 例如: 5+2/1x3 2 ...

  7. 201903-2 CCF 二十四点

    题面: 考场写的30分== #include<bits/stdc++.h> using namespace std; stack<int>st; stack<char&g ...

  8. CSP201903-2二十四点

    如图所示先处理乘号和除号,再处理加减. #include<bits/stdc++.h> using namespace std; ];int main(){ int n; cin>& ...

  9. 【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/16384009 作者:毛星云 ...

随机推荐

  1. 将 Shiro 作为应用的权限基础 一:shiro的整体架构

    将 Shiro 作为应用的权限基础 一:shiro的整体架构 近来在做一个重量级的项目,其中权限.日志.报表.工作量由我负责,工作量还是蛮大的,不过想那么多干嘛,做就是了. 这段时间,接触的东西挺多, ...

  2. 如何防止cookie被串改

    在这里我不想多说怎么去操作cookie了,网上博文一大堆,大家可以去自行搜索,在这里也是记录一下自己的知识,以便以后方便查阅.当我们在浏览器地址栏输入地址成功打开网页以后,服务器会把一些信息写入coo ...

  3. Eclipse配置类似sublime的黑色主题

    另一篇中,详细介绍了如何使用Eclipse+Pydev搭建Python环境,传送门:http://www.cnblogs.com/BH8ANK/p/8688110.html 下面介绍下如何在Eclip ...

  4. Hibernate——配置并访问数据库

    Hibernate,对于java来说很重要的一个东西,用于持久层.之前看了很多配置的,都不行,自己来写一个配置成功的. 环境:jdk1.8,eclipse-jee-oxygen,mysql-conne ...

  5. 游戏安全有多重要?——GAME-TECH游戏开发者技术沙龙

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由云+社区运营团队发布在腾讯云+社区 腾讯云GAME-TECH沙龙继3月深圳站后,将于4月13日来到北京站,与游戏厂商和游戏开发者,畅聊 ...

  6. Idea简单SpringMVC框架配置

    前边已经介绍过了Struts在Idea上的配置,相对于Struts来说,我觉得SpringMVC有更多的优势,首先Struts是需要对action进行配置,页面发送不同的请求,就需要配置不同的acti ...

  7. [福大软工] W班 第2次成绩排行榜

    作业链接 https://edu.cnblogs.com/campus/fzu/FZUSoftwareEngineering1715W/homework/866 评分细则 本次个人项目分数由三部分组成 ...

  8. alpha-咸鱼冲刺day9

    一,合照 emmmmm.自然还是没有的. 二,项目燃尽图 三,项目进展 大概差不多了.不过提交似乎又出了问题正在修改ing 四,问题困难 页面整合啥的凑合一下.因为后面跟着学长速成的PHP,现在还在研 ...

  9. win7开启wifi

    在启用本地共享连接时,出现的错误! 我已经建了一个无线临时网络,来启用共享用来上网的!Internet连接共享访问被启用时,出现了一个错误(null)?而且这错误也会在系统日志里留下记录,都是些莫名其 ...

  10. 20145237 实验二 “Java面向对象程序设计”

    20145237 实验二 “Java面向对象程序设计” 实验内容 • 理解并掌握面向对象三要素:封装.继承.多态 • 初步掌握UML建模 • 熟悉S.O.L.I.D原则 • 使用TDD设计实现复数类 ...