{x}
blog image

24 Game

You are given an integer array cards of length 4. You have four cards, each containing a number in the range [1, 9]. You should arrange the numbers on these cards in a mathematical expression using the operators ['+', '-', '*', '/'] and the parentheses '(' and ')' to get the value 24.

You are restricted with the following rules:

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

Return true if you can get such expression that evaluates to 24, and false otherwise.

 

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

Solution Explanation: 24 Game

The 24 Game problem challenges us to determine if a given set of four cards (numbers 1-9) can be arranged using arithmetic operations (+, -, *, /) and parentheses to equal 24. The solution presented uses Depth-First Search (DFS) to explore all possible combinations of operations and arrangements.

Approach:

The core idea is to recursively explore all possible ways to combine the numbers. The DFS function (dfs in the code examples) handles this recursion:

  1. Base Case: If there's only one number left (nums.length == 1), check if it's approximately equal to 24 (allowing for floating-point precision issues). If it is, return true. Otherwise, return false.

  2. Recursive Step: Iterate through all pairs of numbers (i and j) in the current list nums. For each pair:

    • Create a new list nxt containing the remaining numbers (excluding nums[i] and nums[j]).
    • Iterate through all four arithmetic operators (ops).
    • Perform the operation (nums[i] op nums[j]).
    • Append the result to nxt.
    • Recursively call dfs(nxt). If the recursive call returns true, it means a solution has been found, so return true.
    • If the recursive call returns false, remove the last element from nxt (backtrack) and continue to the next operator.
  3. Return false: If no combination leads to 24 after exploring all possibilities, return false.

Code Structure (Python example):

The Python code efficiently implements this approach:

class Solution:
    def judgePoint24(self, cards: List[int]) -> bool:
        def dfs(nums: List[float]):
            # ... (code as shown previously) ...
        ops = ("+", "-", "*", "/")
        nums = [float(x) for x in cards]
        return dfs(nums)
  • The judgePoint24 function initializes the nums list with floating-point values of card numbers to handle potential divisions.
  • It calls the recursive dfs function to start the search.

Time Complexity Analysis:

The time complexity is dominated by the nested loops within the dfs function. We have:

  • Outer loops: choosing pairs of numbers (O(n²), where n is the number of cards, which is 4 in this case).
  • Inner loop: iterating through operators (O(4)).
  • Recursive calls: In the worst case, the DFS explores a significant portion of the search space, although the exact number is difficult to calculate precisely due to the backtracking. However, it's bounded by the number of possible arrangements and operations.

Therefore, the overall time complexity is approximately O(n² * 4 * k), where k represents the branching factor in the recursion, and n is constant (4). This is essentially exponential, although practically it explores a manageable search space for four cards.

Space Complexity Analysis:

The space complexity is determined by the recursion depth. In the worst case, the recursion depth is proportional to the number of cards (4). The space used for storing lists like nums and nxt is also linear with respect to the number of cards.

Thus, the space complexity is O(n), which is linear, mainly due to the recursive call stack.

The provided code in Java, C++, Go, and TypeScript follows the same logical structure and algorithm, differing only in syntax. The time and space complexity analyses remain consistent across all languages.