24 Game

Updated on 14 May, 2025
24 Game header image

Problem Statement

Given an integer array named cards with exactly four elements, each containing a number between 1 and 9, your task is to determine whether you can arrange these numbers in a mathematical expression using basic arithmetic operations (+, -, *, /) and parentheses to achieve a sum of 24. The particular constraints on this arrangement involve using real division (not integer division), and ensuring that all used operations are binary (involve two numbers). Unary negation (using-' as a negator) and concatenation of numbers are not permitted.

Examples

Example 1

Input:

cards = [4,1,8,7]

Output:

true

Explanation:

(8-4) * (7-1) = 24

Example 2

Input:

cards = [1,2,1,2]

Output:

false

Constraints

  • cards.length == 4
  • 1 <= cards[i] <= 9

Approach and Intuition

The task involves checking various permutations of the numbers in the cards array combined with possible operations and parenthesis placements. Here’s a structured approach to solve this:

  1. Understanding the rules:

    • Each operation is between two numbers (binary operations).
    • Only the four basic operations are allowed and all numbers must be used exactly once in the expression.
    • Parentheses can be used to alter the order of operations, which is crucial for achieving the target number 24.
  2. Permutations and Operations:

    • First, consider all permutations of the cards to account for all possible orderings of numbers.
    • For each permutation, insert the four operations in various combinations.
    • Utilize parentheses in different configurations to prioritize operations.
  3. Evaluating expressions:

    • Evaluate each generated expression to check if it equals to 24.
    • The calculations need to respect the standard mathematical order of operations unless overridden by parentheses.
  4. Algorithm efficiency concerns:

    • The brute-force method of trying every combination might seem feasible given that we only have four numbers, leading to 4! (24) permutations.
    • Including operations and different parenthetical groupings, the number of calculations to test grows, but remains computationally manageable due to the limited initial set size.
  5. Implementation hints:

    • Recursive function can systematically attempt to apply operations between any two results within the current set of remaining numbers.
    • Start by reducing the problem from four numbers to three by selecting two to combine, then from three to two, and finally from two to one resultant number, checking at each stage for the number 24.

By using a recursive combination of these permutations and operations with careful implementation of the standard rules of arithmetic, we can efficiently determine if a set of four numbers can be used to express the number 24.

Solutions

  • C++
  • Java
  • JavaScript
  • Python
cpp
class Solver {
public:
    // Compute all possible outcomes of two operands
    vector<double> computeResults(double& x, double& y) {
        vector<double> outcomes = { x + y, x - y, y - x, x * y };
        if (x != 0) {
            outcomes.push_back(y / x);
        }
        if (y != 0) {
            outcomes.push_back(x / y);
        }
        return outcomes;
    }
    
    // Determine if we can achieve the target using the current numbers
    bool canAchieveTarget(vector<double> nums) {
        if (nums.size() == 1) {
            return abs(nums[0] - 24) < 0.1;
        }

        for (int i = 0; i < nums.size(); i++) {
            for (int j = i + 1; j < nums.size(); j++) {
                vector<double> remaining;
                for (int k = 0; k < nums.size(); k++) {
                    if (k != i && k != j) {
                        remaining.push_back(nums[k]);
                    }
                }
                
                for (double& result : computeResults(nums[i], nums[j])) {
                    remaining.push_back(result);
                    if (canAchieveTarget(remaining)) {
                        return true;
                    }
                    remaining.pop_back();
                }
            }
        }
        return false;
    }
    
    bool judge24(vector<int>& nums) {
        vector<double> doubleNums(nums.begin(), nums.end());
        return canAchieveTarget(doubleNums);
    }
};

This C++ program is designed to solve the "24 Game" problem, where the goal is to determine if it's possible to make the number 24 using all four numbers of the list exactly once and the operations +, -, *, /. The implementation involves several key functions:

  • computeResults(double& x, double& y): This function generates all possible outcomes from two numbers. It handles addition, subtraction, multiplication, and division, ensuring no division by zero occurs.

  • canAchieveTarget(vector<double> nums): This recursive function checks if it's possible to achieve the target, 24, by exploring all permutations of arithmetic operations among the numbers. It selects two numbers from the list, computes all possible results from these two numbers, and then attempts to reach 24 using these results and the remaining numbers. If the size of the list is reduced to one and the absolute difference between the number and 24 is less than 0.1, it returns true, indicating success.

  • judge24(vector<int>& nums): This function serves as the entry point for checking a set of integers. It converts the integers to doubles and passes them to the canAchieveTarget function.

Key aspects of the solution include:

  • Handling permutations and combinations of numbers and operations effectively.
  • Managing floating point operations and comparisons sensibly to deal with precision issues (as in checking for an almost-zero difference rather than exact equality).

This structure demonstrates a clear approach to breaking down the problem into manageable parts, using recursion effectively to explore possible combinations and making intelligent use of mathematics and programming techniques.

java
class Solution {
    private List<Double> calculatedValues(double num1, double num2) {
        List<Double> results = new ArrayList<>();
        results.add(num1 + num2);
        results.add(num1 - num2);
        results.add(num2 - num1);
        results.add(num1 * num2);
        if (num2 != 0) {
            results.add(num1 / num2);
        }
        if (num1 != 0) {
            results.add(num2 / num1);
        }
        return results;
    }

    private boolean canReachTwentyFour(List<Double> values) {
        if (values.size() == 1) {
            return Math.abs(values.get(0) - 24) <= 0.1;
        }

        for (int i = 0; i < values.size(); i++) {
            for (int j = i + 1; j < values.size(); j++) {
                List<Double> nextValues = new ArrayList<>();
                for (int k = 0; k < values.size(); k++) {
                    if (k != j && k != i) {
                        nextValues.add(values.get(k));
                    }
                }

                for (double result : calculatedValues(values.get(i), values.get(j))) {
                    nextValues.add(result);
                    if (canReachTwentyFour(nextValues)) {
                        return true;
                    }
                    nextValues.remove(nextValues.size() - 1);
                }
            }
        }
        return false;
    }

    public boolean judgePoint24(int[] cards) {
        List<Double> initialValues = new ArrayList<>();
        for (int card : cards) {
            initialValues.add((double) card);
        }

        return canReachTwentyFour(initialValues);
    }
}

The provided solution is a Java program designed to solve the 24 Game challenge. The game involves determining whether you can perform arithmetic operations on four numerical values to arrive at the number 24.

Here's a breakdown of the solution approach:

  • Class and Method Structure: The code consists of a class named Solution which has two main private methods (calculatedValues and canReachTwentyFour) and a public method judgePoint24.
  • calculatedValues Method: This method takes two numbers as input and generates all possible outcomes from adding, subtracting, multiplying, or dividing these numbers, except division by zero. The outcomes are stored in a list of doubles and returned.
  • canReachTwentyFour Method: This recursive method receives a list of doubles (initial values from the game cards). The base case checks if the list contains exactly one value that approximates 24. The method then attempts to reduce the problem size by combining two values at a time using the calculatedValues method and recursively checking if the new set can reach 24.
  • judgePoint24 Method: This is the entry method which converts the initial integer array of card values into doubles and uses canReachTwentyFour on them to decide if 24 can be reached.

Overall, the approach utilizes recursion and backtracking by continuously reducing the problem size through operations on pairs of numbers until a solution is reached or all possibilities are exhausted. The use of recursion allows for exploring all potential combinations dynamically.

Edit this Java class to implement or modify functionalities specific to your requirement or to optimize any specific steps according to the criteria or constraints particular to a given set of card inputs.

js
// Function to calculate all possible mathematical results between two numbers
let calculateResults = (num1, num2) => {
    let outcomes = [num1 + num2, num1 - num2, num2 - num1, num1 * num2];
    if (num1) {
        outcomes.push(num2 / num1);
    }
    if (num2) {
        outcomes.push(num1 / num2);
    }
    return outcomes;
}

// Recursive function to determine if we can achieve the target number 24
let canAchieveTarget = numbers => {
    if (numbers.length === 1) {
        // Only one number remains; check if it's approximately 24
        return Math.abs(numbers[0] - 24.0) <= 0.1;
    }

    for (let i = 0; i < numbers.length; i++) {
        for (let j = i + 1; j < numbers.length; j++) {
            // Form a new list excluding the two elements used for calculation
            let newNumbers = [];
            for (let k = 0; k < numbers.length; k++) {
                if (k !== i && k !== j) {
                    newNumbers.push(numbers[k]);
                }
            }
            
            // Perform all operations between the selected pair of numbers
            let potentialResults = calculateResults(numbers[i], numbers[j]);
            for (let result of potentialResults) {
                // Include the new result and recurse
                newNumbers.push(result);
                
                // Recursive check with the updated list
                if (canAchieveTarget(newNumbers)) {
                    return true;
                }
                
                // Remove the result to backtrack
                newNumbers.pop();
            }
        }
    }
    return false;
};

// Wrapper function to initiate the process with card values
let check24Game = cardValues => canAchieveTarget(cardValues);

The JavaScript solution provided attempts to solve the "24 Game" problem, where the objective is to determine if any permutation and combination of four numbers and the basic arithmetic operations (+, -, *, /) can result in the value 24. The solution utilizes recursion and backtracking for this purpose.

The core of the implementation revolves around two primary functions:

  • calculateResults(num1, num2): This function generates all possible outcomes from two numbers using the operations addition, subtraction, multiplication, and division. It ensures that division by zero does not occur.

  • canAchieveTarget(numbers): Here you check if you can derive the value 24 from a given set of numbers recursively. Starting with the full set of numbers, the function tries various combinations of pairs and operations. It uses recursive calls to further explore each possibility by including a new number generated from a previous operation. The recursion continues until it either confirms it's possible to achieve 24 or exhausts all combinations.

The solution is executed by the check24Game(cardValues) function, which simply starts the recursive process using a list of numbers (typically card values).

The approach is thorough, considering every operation between every pair of numbers and recursively determining if continuing operations on the resulting number can achieve 24. This method leverages two important techniques in computer science—recursion and backtracking—to explore possible operations and backtrack as necessary when a path does not lead to the desired outcome.

python
class Solution:
    def compute_results(self, num1: float, num2: float) -> List[float]:
        outcomes = [num1 + num2, num1 - num2, num2 - num1, num1 * num2]
        if num1:
            outcomes.append(num2 / num1)
        if num2:
            outcomes.append(num1 / num2)
        return outcomes
    
    def can_achieve_target(self, nums: List[float]) -> bool:
        if len(nums) == 1:
            return abs(nums[0] - 24.0) < 0.1
        
        for index in range(len(nums)):
            for next_index in range(index + 1, len(nums)):
                current_list = [value for k, value in enumerate(nums) if k != index and k != next_index]
                for result in self.compute_results(nums[index], nums[next_index]):
                    current_list.append(result)
                    
                    if self.can_achieve_target(current_list):
                        return True
                    
                    current_list.pop()
        
        return False
    
    def judgePoint24(self, nums: List[int]) -> bool:
        return self.can_achieve_target(nums)

The "24 Game" solution posted above utilizes recursion and backtracking to determine if it is possible to perform operations (addition, subtraction, multiplication, division) on a list of four numbers to achieve the result 24.

  • Begin by defining methods in the Solution class that handle different aspects of the problem:

    • compute_results: This method takes two numbers and computes the possible outcomes (addition, subtraction in both orders, multiplication, and conditional division if the divisor is not zero).

    • can_achieve_target: This recursive method evaluates if it is possible to achieve 24 using the current set of numbers. The approach checks all pairs of numbers, computes the potential results, and recursively attempts to achieve the target with the new set of numbers generated by incorporating one of the results from compute_results.

    • judgePoint24: This is the entry point method that receives the initial list of numbers and starts the computation by calling can_achieve_target.

    The algorithm:

    1. Retrieves all outcomes for operations between each pair of numbers using compute_results.
    2. Explores each computed result by inserting it back into the list and checking if the modified list can solve for the target value, 24.
    3. Continues this process recursively until it either finds a solution or exhausts all combinations.

This solution avoids direct use of global variables or any non-local structures except for the number list itself, which is altered and restored during recursion to explore possible solutions. This approach ensures the function is purely based on its inputs, making it robust and flexible for different inputs of four numbers, effectively managing the complexity inherent in the problem.

Comments

No comments yet.