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.
With 4 numbers the search space is small enough to exhaustively try all binary expression trees. Combine two numbers at a time with each operator (including both operand orders for non-commutative ops) until a single value remains and check if it equals 24 (within a small epsilon for floating arithmetic).
Convert inputs to floating-point numbers to allow real division.
If only one number remains, compare it to 24 within an epsilon tolerance.
For every ordered pair (i, j), compute possible results from nums[i] and nums[j] (six values: +, -, reverse -, *, /, reverse / when denominators non-zero).
Replace the pair with the result to form a new list of size n-1 and recurse.
Backtrack on failure and return true as soon as any recursion finds 24.
classSolution:
defjudgePoint24(self, cards: List[int]) -> bool:
defsolve(nums: list[float]) -> bool:
if len(nums) ==1:
return abs(nums[0] -24) <1e-6for i in range(len(nums)):
for j in range(len(nums)):
if i == j:
continue nxt = [nums[k] for k in range(len(nums)) if k != i and k != j]
for res in [
nums[i] + nums[j],
nums[i] - nums[j],
nums[j] - nums[i],
nums[i] * nums[j],
nums[i] / nums[j] if abs(nums[j]) >1e-6else float('inf'),
nums[j] / nums[i] if abs(nums[i]) >1e-6else float('inf')
]:
if res == float('inf'):
continueif solve(nxt + [res]):
returnTruereturnFalsereturn solve([float(x) for x in cards])
⏰ Time complexity:O(1) – For the fixed-size n = 4 input the number of expression trees, permutations and operator choices is constant and bounded. (If expressed for a general input size n, a conservative worst-case upper bound for the brute-force pairwise-combine algorithm is O(n! * 4^{n} * Catalan(n-1)), accounting for permutations of input, operator choices and parenthesizations; this is only for illustrative purposes.)
🧺 Space complexity:O(1) – Recursion depth and temporary storage are bounded by constant factors for n = 4.
Method 1 is the same pairwise-combine search used by the followup; it is practical here because n is fixed and small. For larger n we reuse the same idea but add memoization (pairwise DFS + memo) or a subset-DP formulation to avoid recomputing equivalent intermediate multisets.
Generalizing the 4-card approach, we repeatedly combine two values. To prune duplicate work we canonicalize each multiset of intermediate exact rationals and memoize visited states so identical multisets are not re-explored.
from fractions import Fraction
from typing import List, Tuple
from functools import lru_cache
classSolution:
defcanMakeTarget(self, nums: List[int], target: int) -> bool:
nums_fr = tuple(Fraction(x) for x in nums)
@lru_cache(maxsize=None)
defsolve(state: Tuple[Fraction, ...]) -> bool:
if len(state) ==1:
return state[0] == Fraction(target)
n = len(state)
for i in range(n):
for j in range(i+1, n):
a, b = state[i], state[j]
rest = [state[k] for k in range(n) if k != i and k != j]
results = {a + b, a - b, b - a, a * b}
if b !=0:
results.add(a / b)
if a !=0:
results.add(b / a)
for r in results:
nxt = tuple(sorted(rest + [r]))
if solve(nxt):
returnTruereturnFalsereturn solve(tuple(sorted(nums_fr)))
⏰ Time complexity:O(exponential) – The state space grows super-exponentially; memoization prunes duplicate multisets making the method practical up to about n≈7 in many cases.
🧺 Space complexity:O(number_of_cached_states) – Cache stores canonical states (sorted tuples of Fractions).
⏰ Time complexity:O(S * n^2 * k) – Let n be the number of input values, S be the number of distinct canonical states stored in the memo, and k (≤6) be the maximum number of operator results considered per pair. Each cached state explores O(n^2) unordered pairs and up to k results per pair.
🧺 Space complexity:O(S) – Memory to store cached canonical states (each state is a sorted tuple of up to n exact rationals).
For each subset of indices compute the set of exact values (Fractions) obtainable from that subset. Combine two disjoint subsets by applying all binary operators across their value-sets to form values for the union subset.
// Java Subset DP using exact rationalsimport java.util.*;
import java.math.*;
classRational {
long num, den;
Rational(long a, long b) {
if (b < 0) { a =-a; b =-b; }
if (a == 0) { num = 0; den = 1; }
else {
long g = BigInteger.valueOf(Math.abs(a)).gcd(BigInteger.valueOf(b)).longValue();
num = a / g; den = b / g;
}
}
String str() { return num +"/"+ den; }
@Overridepublicbooleanequals(Object o) {
if (!(o instanceof Rational)) returnfalse;
Rational r = (Rational)o; return num == r.num&& den == r.den;
}
@OverridepublicinthashCode() { return Objects.hash(num, den); }
}
classSolution {
publicbooleancanMakeTarget(int[] nums, int target) {
int n = nums.length;
int N = 1 << n;
List<Set<Rational>> dp =new ArrayList<>(Collections.nCopies(N, null));
for (int i = 0; i < N; ++i) dp.set(i, new HashSet<>());
for (int i = 0; i < n; ++i) dp.get(1<<i).add(new Rational(nums[i], 1));
for (int mask = 0; mask < N; ++mask) {
if (dp.get(mask).isEmpty()) continue;
int sub = mask;
while (sub != 0) {
sub = (sub - 1) & mask;
int other = mask ^ sub;
if (sub == 0 || other == 0) continue;
for (Rational va : dp.get(sub)) {
for (Rational vb : dp.get(other)) {
dp.get(mask).add(new Rational(va.num* vb.den+ vb.num* va.den, va.den* vb.den));
dp.get(mask).add(new Rational(va.num* vb.den- vb.num* va.den, va.den* vb.den));
dp.get(mask).add(new Rational(vb.num* va.den- va.num* vb.den, va.den* vb.den));
dp.get(mask).add(new Rational(va.num* vb.num, va.den* vb.den));
if (vb.num!= 0) dp.get(mask).add(new Rational(va.num* vb.den, va.den* vb.num));
if (va.num!= 0) dp.get(mask).add(new Rational(vb.num* va.den, vb.den* va.num));
}
}
}
}
return dp.get(N-1).contains(new Rational(target, 1));
}
}
from fractions import Fraction
from typing import List, Set
classSolution:
defcanMakeTarget(self, nums: List[int], target: int) -> bool:
n = len(nums)
N =1<< n
dp: List[Set[Fraction]] = [set() for _ in range(N)]
for i in range(n):
dp[1<< i].add(Fraction(nums[i]))
for mask in range(N):
ifnot dp[mask]:
continue sub = mask
while sub:
sub = (sub -1) & mask
other = mask ^ sub
if sub ==0or other ==0:
continuefor va in dp[sub]:
for vb in dp[other]:
dp[mask].add(va + vb)
dp[mask].add(va - vb)
dp[mask].add(vb - va)
dp[mask].add(va * vb)
if vb !=0:
dp[mask].add(va / vb)
if va !=0:
dp[mask].add(vb / va)
return Fraction(target) in dp[N -1]
⏰ Time complexity:O(n * 2^n * s^2) – Let n be the number of input values and s be the average number of distinct Fraction/Rational values stored per mask. For each of the 2^n masks we consider O(n) submask splits and combine O(s^2) pairs of values.
🧺 Space complexity:O(2^n * s) – We store s values for each of the 2^n masks.