题解 | #24点游戏算法#

24点游戏算法

http://www.nowcoder.com/practice/fbc417f314f745b1978fc751a54ac8cb

描述

题目描述

给出4个1-10的数字,通过加减乘除运算,得到数字为24就算胜利,除法指实数除法运算,本题对数字选取顺序无要求,但每个数字仅允许使用一次,且不考虑括号运算

此题允许数字重复,如3 3 4 4为合法输入,但是每个数字只允许使用一次,如此处一共有两个3,则运算过程中两个3都被选取计算一次,所以3被调用运算两次,但是对应读入的两个数字

示例

输入:
7 2 1 10
输出:
true

知识点:搜索,DFS,回溯

难度:⭐⭐⭐


题解

方法一:DFS

图解

image-20211209224617166

算法流程

  • 通过递归+dfs,首先定义递归函数功能:当前已经使用了数组的u个数字,前面数字的运算结果为tmp,返回是否运算结果为24
  • 确定递归终止条件:已经使用了数组四个元素,同时结果为24,满足题意
  • 通过加减乘除四种运算,加减乘除当前数字num[i],不断递归运算,直到已经使用了数组四个元素,同时结果为24,满足题意

Java 版本代码如下:

import java.util.*;
import java.io.*;
public class Main{
    static int[] nums = new int[4];
    static boolean[] visit = new boolean[4];
    static int flag = 0;
    
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        while(in.hasNext()){
            String[] a = in.nextLine().split(" ");
            for(int i = 0; i < 4; i ++)
                nums[i] = Integer.parseInt(a[i]);
            dfs(0, 0);   
            System.out.println( flag == 1 );
        }

    }

    // tmp是前面n个数字运算结果,u表示已经使用了多少个数字
    static boolean dfs(int u,float tmp){
        // 递归终止条件:已经使用了数组四个元素,同时结果为24,满足题意
        if(tmp == 24 && u == 4){
            flag = 1;
            return true;
        }

        for(int i = 0; i < 4; i ++){
            if(visit[i] == false){
                visit[i] = true;
                // 加减乘除当前数字num[i]
                if( dfs(u + 1, tmp + nums[i]) ||
                   dfs(u + 1, tmp - nums[i])  ||
                   dfs(u + 1,tmp * nums[i]) ||
                   dfs(u + 1, tmp / nums[i])){
                    return true;
                }
                // 相当于回溯
                visit[i] = false;
            }
        }
        return false;
    }
}


复杂度分析

时间复杂度O(1)O(1),因为n=4,则我们即便搜索过所有的情况,那么我们最后的次数也是一个常数级别

空间复杂度O(1)O(1),因为n=4,而且我们的递归栈使用的空间也是非常小,是一个常数级别

方法二:暴力穷举

版本代码如下:

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

vector<double> a(4);
vector<char> op = { '+', '-', '*', '/' };
vector<vector<char>> all;
// a数组是用来存储我们输入的四位数字的
// 我们的op数组是我们的四个符号位的一个数组
// 我们这个表达式里面一共是需要三个符号的
// 我们的这个all就是我们四个选项选三个然后排列组合的所有的可能

void init()
{
    for (auto& it1 : op) { 
        // it1代表的是我们第一个的符号位是什么
        for (auto& it2 : op) {
            // it2代表的是我们第二个的符号是什么
            for (auto& it3 : op) {
                // it3代表的是我们第三个符号位是什么
                vector<char> tmp(3);
                // 这里我们开辟的tmo是我们临时的一个存储三个符号位的数组
                tmp[0] = it1, tmp[1] = it2, tmp[2] = it3;
                // 我们将我们枚举出来的符号位放到这个数组里面
                all.push_back(tmp);
                // 然后将我们的可能塞入我们的这个all的数组中
            }
        }
    }
}

double cal(double a, double b, char op)
{
    switch (op) {
    case '+':
        return a + b;
    case '-':
        return a - b;
    case '*':
        return a * b;
    case '/':
        return a / b;
    }
    return 1;
}
// 这个是用来计算值的,判断我们的符号然后进行一个运算

void solve()
{
    sort(a.begin(), a.end());
    // 因为我们的c++内的全排列函数要求是要从小到大的数组,所以我们排一下序
    do {
        for (auto &it : all) {
            // 遍历所有的符号集的所有可能
            double res = a[0];
            // 因为我们有四个数字,但是只有三个符号,所以我们先把答案设置为第一个数字
            int index = 0;
            // 这个是我们符号数组的下标
            for (int i = 1; i <= 3; i++)
                res = cal(res, a[i], it[index++]);
            // 计算我们当前这个符号集下的答案会是多少
            if (res == 24) {
                cout << "true\n";
                return; 
            }
            // 如果已经是等于了24,那么我们可以不用管其他的了,直接就是可以把他输出,然后退出即可
        }
    } while (next_permutation(a.begin(), a.end()));
    // 这个是我们执行了一个全排列
    cout << "false\n";
    // 执行完了所有的都没有答案,我们输出false
}

signed main()
{
    init();
    while (cin >> a[0] >> a[1] >> a[2] >> a[3]) {
        solve();
    }
    return 0;
}

复杂度分析

时间复杂度O(1)O(1),因为n=4,最后的全排列后,也是一个常数

空间复杂度O(1)O(1),因为n=4,n非常的小,所以我们的空间复杂度可以看成O(1)

华为机试 文章被收录于专栏

华为机试题解

全部评论
没有考虑括号的情况,这道题的算例太少了所以可以通过。比如 5 7 1 1 ((5+7)*(1+1))的情况就会输出false
6 回复
分享
发布于 2022-04-04 10:09
要优化下,n=0 时,- * / 不能参与运算,二且这种是不是是没有考虑括号情况
2 回复
分享
发布于 2022-02-26 21:43
滴滴
校招火热招聘中
官网直投
这个方法是不对的,因为在递归前 0参与了运算,即0+x+x+x+x,对应加号没影响,但是对于除法,有影响,比如给定8 8 8 8这个代码给的运算方式是0/8+8+8+8,然后返回的结果运算式结果是24,true。但是实际上只用4个8运算是得不到24的,结果应该是false。所以就java代码而言,代码是错误的。而且一次循环结束flag没有初始化,导致只要为true,后面无法再进行新一轮的判定
9 回复
分享
发布于 2022-02-19 20:00
java版本 while (scanner.hasNext())里要加flag = 0初始化
点赞 回复
分享
发布于 2022-02-10 17:23
这题要考虑括号的吧 :本题对数字选取顺序无要求,但每个数字仅允许使用一次,且需考虑括号运算
点赞 回复
分享
发布于 2022-06-28 15:29
import java.util.Scanner; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main { public static boolean[] visited = {false,false,false,false}; public static int[] nums = new int[4]; public static void main(String[] args) { Scanner in = new Scanner(System.in); // 注意 hasNext 和 hasNextLine 的区别 while (in.hasNext()) { // 注意 while 处理多个 case for(int i=0;i<4;i++){ nums[i] = in.nextInt(); } //若考虑括号的情况,需要对数组再全排列,这里不写也能过 boolean flag = false; for(int i=0;i<4;i++){ visited[i] = true; if(cal(1,(double)nums[i])){ flag = true; break; }else{ visited[i] = false; } } System.out.print(flag); } } public static boolean cal(int used, double tempNum){ if(tempNum == 24.0d && used == 4){ return true; } for(int i=0;i<4;i++){ if(visited[i] == false){ visited[i] = true; if(cal(used+1,tempNum+nums[i]) || cal(used+1,tempNum-nums[i]) || cal(used+1,tempNum*nums[i]) || cal(used+1,tempNum/nums[i]) ){ return true; }else{ visited[i] = false; } } } return false; } } 稍作修改可过
点赞 回复
分享
发布于 2022-07-13 22:26
4 1 9 1 这种情况通不过
点赞 回复
分享
发布于 2022-07-27 00:35
8 8 8 8 5 9 7 1 都是反例
点赞 回复
分享
发布于 2023-01-04 16:57 上海
暴力穷举是C++么?
点赞 回复
分享
发布于 2023-05-14 18:09 广东
7 9 10 9 能过 8 8 8 8 需要带括号才能过 5 7 1 1 需要带括号才能过
点赞 回复
分享
发布于 2023-10-05 12:18 陕西
6 9 5 6:(6*9)-(5*6)这种情况也应该考虑到啊。 但是居然能过我是没想到的。
点赞 回复
分享
发布于 2023-12-13 00:05 湖南
楼主的代码解决方向是对的,但还需要雕琢。比如最初tmp=0使只能做加法,0加第一个数,相当于取第一个数开始运算。 其次是还要考虑括号,两两运算,再互相运算,一样用递归+回溯就行了。
点赞 回复
分享
发布于 2023-12-13 00:36 湖南
等我明天把代码实现一下 嘿嘿
点赞 回复
分享
发布于 2023-12-13 00:36 湖南

相关推荐

点赞 评论 收藏
转发
13 1 评论
分享
牛客网
牛客企业服务