跳到主要内容

HJ67. 24点游戏算法

题目描述​

给出 4 个 1~10 的正整数,使用加减乘除运算(+ - * /),每个数字必须且只能用一次,判断是否能组成表达式结果为 24(除法为实数除法)。数字可以通过括号改变优先级。

输入格式​

一行 4 个正整数。

输出格式​

能组成 24 输出 true,否则输出 false。

示例​

示例 1​

输入:

7 2 1 10

输出:

true

示例 2​

输入:

1 1 1 1

输出:

false

解题思路​

第一步:理解问题本质​

4 个数通过 + - × ÷ 和括号组合,所有数必须用完。等价于:任选两个数做一次运算,结果放回,递归处理 3 个数、2 个数……直到 1 个数时判断是否等于 24。

第二步:暴力解法​

枚举所有括号结构(5 种)和所有运算排列,再枚举数字排列。共 4! × 4³ × 5 ≈ 数千种,虽可枚举但代码繁琐。

第三步:DFS + 回溯(最优)​

用递归把问题变小:每次从剩余数字中取两个数 a、b,尝试所有二元运算得到结果,放回集合继续递归,直到只剩一个数判断是否等于 24。

关键点:

  • 减法/除法需要尝试两个方向:a-b、b-a、a/b、b/a(分母不为 0)。
  • 除法为实数除法,浮点数比较用容差 |x - 24| < 1e-6。
  • 分支数有限(4 个数全排列组合),很快结束。
def can_reach_24(nums):
if len(nums) == 1:
return abs(nums[0] - 24) < 1e-6
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
a, b = nums[i], nums[j]
rest = [nums[k] for k in range(len(nums)) if k != i and k != j]
candidates = [a + b, a - b, b - a, a * b]
if b != 0: candidates.append(a / b)
if a != 0: candidates.append(b / a)
for val in candidates:
if can_reach_24(rest + [val]):
return True
return False

完整代码实现​

算法可视化 · 逐帧回放这道题的代码126 帧 · 录制输入 [7, 2, 1, 10]
import sys


def can_reach_24(nums):
if len(nums) == 1:
return abs(nums[0] - 24) < 1e-6
n = len(nums)
for i in range(n):
for j in range(i + 1, n):
a, b = nums[i], nums[j]
rest = [nums[k] for k in range(n) if k != i and k != j]
candidates = [a + b, a - b, b - a, a * b]
if b != 0:
candidates.append(a / b)
if a != 0:
candidates.append(b / a)
for val in candidates:
if can_reach_24(rest + [val]):
return True
return False


def solve():
data = sys.stdin.buffer.read().split()
nums = [int(x) for x in data]
print("true" if can_reach_24(nums) else "false")


if __name__ == "__main__":
solve()

示例推演​

以 3 3 8 8 为例(能组成 24):

  • 取 3 和 8/3:先算 8/3 = 2.666...
  • 剩 [3, 2.666...]
  • 3 - 2.666... = 0.333...,剩 [8, 0.333...]
  • 8 / 0.333... = 24 ✓

表达式为 8 / (3 - 8/3) = 24,输出 true。

再以 1 1 1 1 为例:无论怎么运算,1 与 1 的组合结果最大也就 1×1×1×1=1,永远达不到 24,输出 false。


复杂度分析​

解法时间复杂度空间复杂度说明
枚举括号结构O(5·4!·4³)O(1)代码繁琐
DFS + 回溯O(4!·4³)O(4)递归深度有限,最优

数字仅 4 个,规模恒定,两种都能在常数时间内完成


易错点总结​

1. 减法和除法两个方向​

a-b 与 b-a、a/b 与 b/a 结果不同,必须都尝试,否则漏解。

2. 除数为 0​

a/b 需保证 b != 0,b/a 需保证 a != 0,否则除零报错。

3. 浮点数比较​

实数除法产生浮点误差,不能用 == 24,要用容差 abs(x - 24) < 1e-6。

4. 每个数字必须且只能用一次​

取走 a、b 后要用 rest 排除它们,不能重复使用。


扩展思考​

  • 本题可扩展到 n 个数字凑任意目标值,只需调整目标和递归。
  • 若需要输出表达式而非判断,可在递归中记录操作历史。

相关题目​

自测​

加载评论中...