#每日一题19 回溯 + 全排列思想

大家好,我是你的CSDN技术博主,今天是2026年1月13日,继续每日一题!

今天分享一道经典的困难 难度回溯题目------679. 24 点游戏 。这道题是面试中非常高频的"24点"变种,考察全排列 + 回溯 + 浮点精度处理,思路清晰但细节较多,强烈推荐手写几遍!

一、题目理解

题目描述

给你一个长度为 4 的整数数组 cards,每个元素在 1~9 之间。

你可以使用 +、-、×、÷ 四种运算符,对这 4 个数字进行任意顺序、任意括号组合的运算,问是否能恰好得到 24

返回 truefalse

注意

  • 除法结果可以是浮点数,但最终结果必须等于 24(误差 ≤ 1e-5 视为相等)。
  • 除数不能为 0。
  • 运算顺序和括号可以任意组合。

示例解析

示例 1:

输入:cards = [4,1,8,7]

输出:true

解释:(8-4) × (7-1) = 4 × 6 = 24

示例 2:

输入:cards = [1,2,1,2]

输出:false

无法得到 24

示例 3:

输入:cards = [3,3,8,8]

输出:true

解释:8 ÷ (3 - 8/3) = 8 ÷ (3 - 2.666...) = 8 ÷ 0.333... = 24

二、解题思路(回溯 + 暴力枚举)

核心思想

只有 4 个数字,运算顺序和括号组合总数有限(卡特兰数级别),可以用暴力回溯枚举所有可能。

步骤:

  1. 每次从当前剩余数字中选 2 个数字 a、b
  2. 对 a、b 枚举 6 种运算(+、-×2、×、/×2)
  3. 将运算结果替换回列表(数字数量减 1)
  4. 递归处理剩余数字,直到只剩 1 个数
  5. 判断最后结果是否 ≈ 24(误差 ≤ 1e-6)

关键优化 & 注意点

  • 除法要同时尝试 a/b 和 b/a,且除数不能为 0
  • 加法和乘法满足交换律,可只试一次(但代码中为了统一,通常都写)
  • 用 float 计算,避免整数除法截断
  • 判断相等时用 abs(result - 24) < 1e-61e-9

时间复杂度:O(4! × 4^3) ≈ 几千次运算,极快

三、代码演示(Java 与 Python 双版本)

Java 版本(完整回溯)

java 复制代码
import java.util.*;

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

    private boolean backtrack(List<Double> nums) {
        if (nums.size() == 1) {
            return Math.abs(nums.get(0) - 24) < 1e-6;
        }

        for (int i = 0; i < nums.size(); i++) {
            for (int j = 0; j < nums.size(); j++) {
                if (i == j) continue;

                double a = nums.get(i);
                double b = nums.get(j);

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

                // 加法
                newNums.add(a + b);
                if (backtrack(newNums)) return true;
                newNums.remove(newNums.size() - 1);

                // 减法 a - b
                newNums.add(a - b);
                if (backtrack(newNums)) return true;
                newNums.remove(newNums.size() - 1);

                // 减法 b - a
                newNums.add(b - a);
                if (backtrack(newNums)) return true;
                newNums.remove(newNums.size() - 1);

                // 乘法
                newNums.add(a * b);
                if (backtrack(newNums)) return true;
                newNums.remove(newNums.size() - 1);

                // 除法 a / b
                if (Math.abs(b) > 1e-9) {
                    newNums.add(a / b);
                    if (backtrack(newNums)) return true;
                    newNums.remove(newNums.size() - 1);
                }

                // 除法 b / a
                if (Math.abs(a) > 1e-9) {
                    newNums.add(b / a);
                    if (backtrack(newNums)) return true;
                    newNums.remove(newNums.size() - 1);
                }
            }
        }
        return false;
    }
}

Python 版本(简洁回溯)

python 复制代码
from typing import List

class Solution:
    def judgePoint24(self, cards: List[int]) -> bool:
        def backtrack(nums: List[float]) -> bool:
            if len(nums) == 1:
                return abs(nums[0] - 24) < 1e-6

            for i in range(len(nums)):
                for j in range(len(nums)):
                    if i == j:
                        continue
                    a, b = nums[i], nums[j]
                    new_nums = [nums[k] for k in range(len(nums)) if k != i and k != j]

                    # 加法
                    if backtrack(new_nums + [a + b]):
                        return True
                    # 减法 a-b
                    if backtrack(new_nums + [a - b]):
                        return True
                    # 减法 b-a
                    if backtrack(new_nums + [b - a]):
                        return True
                    # 乘法
                    if backtrack(new_nums + [a * b]):
                        return True
                    # 除法 a/b
                    if abs(b) > 1e-9 and backtrack(new_nums + [a / b]):
                        return True
                    # 除法 b/a
                    if abs(a) > 1e-9 and backtrack(new_nums + [b / a]):
                        return True

            return False

        return backtrack([float(x) for x in cards])

四、代码解读

  • 回溯核心:每次选两个数 → 枚举 6 种运算 → 递归处理 n-1 个数
  • 浮点处理:输入转 float,除法用 float 除
  • 精度判断abs(result - 24) < 1e-61e-9
  • 剪枝:除数接近 0 时跳过

五、复杂度分析

  • 时间复杂度:O(4! × 4^3) ≈ 几千次运算,极快
  • 空间复杂度:O(递归深度) ≈ O(4)

六、总结

这道题是暴力回溯解决排列组合问题的经典案例。核心技巧:

  • 每次选 2 个数 → 枚举运算 → 缩减为 n-1 个数
  • 递归到 1 个数时判断是否 ≈ 24

掌握这个模板,能轻松应对:

  • 类似"四则运算得到目标值"的搜索题
  • 面试中的"24点"变种

类似题目推荐:

  • LeetCode 282. 给表达式添加运算符
  • LeetCode 294. 翻转游戏 II
  • LeetCode 464. 我能赢吗(状态压缩 + 博弈)

如果本文对你有帮助,欢迎点赞、收藏、关注~

明天每日一题再见!

相关推荐
Benny_Tang1 小时前
题解:CF2164C Dungeon
c++·算法
仙俊红2 小时前
LeetCode174双周赛T3
数据结构·算法
Echoo华地2 小时前
idea运行程序默认线程为daemon线程的问题
java·ide·intellij-idea
满栀5852 小时前
分页插件制作
开发语言·前端·javascript·jquery
froginwe112 小时前
C 标准库 - <stdio.h>
开发语言
zwtahql2 小时前
php源码级别调试
开发语言·php
歪楼小能手2 小时前
Android16系统go版关闭重力旋转开关后缺失手动旋转屏幕悬浮按钮
android·java·平板
qq_406176142 小时前
深入剖析JavaScript原型与原型链:从底层机制到实战应用
开发语言·前端·javascript·原型模式
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-DDD业务领域模块设计思路
java·数据库·人工智能·spring boot·ddd