Hard
题目描述
给定一个长度为 4 的整数数组 cards。你有四张卡片,每张卡片上都有一个范围在 [1, 9] 的数字。你需要用数学表达式中的运算符 ['+', '-', '*', '/'] 以及括号 '(' 和 ')' 来排列这些卡片上的数字,使得表达式的值等于 24。
你需要遵循以下规则:
- 除法运算符
'/'表示实数除法,不是整数除法。- 例如,
4 / (1 - 2 / 3) = 4 / (1 / 3) = 12。
- 例如,
- 每次操作都是在两个数字之间进行的。特别是,我们不能将
'-'用作一元运算符。- 例如,如果
cards = [1, 1, 1, 1],表达式"-1 - 1 - 1 - 1"是不被允许的。
- 例如,如果
- 你不能将数字连接在一起
- 例如,如果
cards = [1, 2, 1, 2],表达式"12 + 12"是无效的。
- 例如,如果
如果能够得到这样的表达式,使其计算结果为 24,则返回 true,否则返回 false。
示例 1:
输入:cards = [4,1,8,7]
输出:true
解释:(8-4) * (7-1) = 24
示例 2:
输入:cards = [1,2,1,2]
输出:false
提示:
cards.length == 41 <= cards[i] <= 9
解题思路
这是一个典型的回溯算法问题。核心思路是通过递归的方式尝试所有可能的数字组合和运算符组合。
解题思路:
- 状态表示:使用一个数组表示当前可用的数字,初始为输入的4个数字
- 递归过程:每次选择两个数字进行运算,得到新的结果,然后递归处理剩余的数字
- 终止条件:当只剩一个数字时,检查是否接近24(考虑浮点数精度问题)
- 回溯操作:尝试所有可能的数字对和四种运算符
关键细节:
- 使用浮点数进行计算,避免整数除法的问题
- 注意除法时分母不能为0
- 由于浮点数精度问题,判断是否等于24时使用epsilon比较
- 加法和乘法满足交换律,减法和除法需要考虑两种顺序
时间复杂度优化: 虽然看似复杂度很高,但由于只有4个数字,实际的搜索空间是有限的,完全可以接受。
代码实现
class Solution {
public:
bool judgePoint24(vector<int>& cards) {
vector<double> nums;
for (int card : cards) {
nums.push_back(card);
}
return backtrack(nums);
}
private:
bool backtrack(vector<double>& nums) {
if (nums.size() == 1) {
return abs(nums[0] - 24) < 1e-6;
}
for (int i = 0; i < nums.size(); i++) {
for (int j = i + 1; j < nums.size(); j++) {
double a = nums[i], b = nums[j];
vector<double> next;
// 将其他数字加入下一轮
for (int k = 0; k < nums.size(); k++) {
if (k != i && k != j) {
next.push_back(nums[k]);
}
}
// 尝试四种运算
vector<double> candidates = {a + b, a - b, b - a, a * b};
if (abs(b) > 1e-6) candidates.push_back(a / b);
if (abs(a) > 1e-6) candidates.push_back(b / a);
for (double candidate : candidates) {
next.push_back(candidate);
if (backtrack(next)) return true;
next.pop_back();
}
}
}
return false;
}
};
class Solution:
def judgePoint24(self, cards: List[int]) -> bool:
def backtrack(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]
# 剩余的数字
remaining = [nums[k] for k in range(len(nums)) if k != i and k != j]
# 尝试所有运算
candidates = [a + b, a - b, b - a, a * b]
if abs(b) > 1e-6:
candidates.append(a / b)
if abs(a) > 1e-6:
candidates.append(b / a)
for candidate in candidates:
if backtrack(remaining + [candidate]):
return True
return False
return backtrack([float(card) for card in cards])
public class Solution {
public bool JudgePoint24(int[] cards) {
var nums = new List<double>();
foreach (int card in cards) {
nums.Add(card);
}
return Backtrack(nums);
}
private bool Backtrack(List<double> nums) {
if (nums.Count == 1) {
return Math.Abs(nums[0] - 24) < 1e-6;
}
for (int i = 0; i < nums.Count; i++) {
for (int j = i + 1; j < nums.Count; j++) {
double a = nums[i], b = nums[j];
var next = new List<double>();
// 添加其他数字
for (int k = 0; k < nums.Count; k++) {
if (k != i && k != j) {
next.Add(nums[k]);
}
}
// 尝试四种运算
var candidates = new List<double> { a + b, a - b, b - a, a * b };
if (Math.Abs(b) > 1e-6) candidates.Add(a / b);
if (Math.Abs(a) > 1e-6) candidates.Add(b / a);
foreach (double candidate in candidates) {
next.Add(candidate);
if (Backtrack(next)) return true;
next.RemoveAt(next.Count - 1);
}
}
}
return false;
}
}
var judgePoint24 = function(cards) {
const eps = 1e-6;
function solve(nums) {
if (nums.length === 1) {
return Math.abs(nums[0] - 24) < eps;
}
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
const a = nums[i];
const b = nums[j];
const remaining = nums.filter((_, idx) => idx !== i && idx !== j);
const operations = [
a + b,
a - b,
b - a,
a * b
];
if (Math.abs(b) > eps) operations.push(a / b);
if (Math.abs(a) > eps) operations.push(b / a);
for (const result of operations) {
if (solve([...remaining, result])) {
return true;
}
}
}
}
return false;
}
return solve(cards);
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(1) | 虽然递归深度和分支因子较大,但由于输入规模固定(4个数字),总的状态空间是常数级的 |
| 空间复杂度 | O(1) | 递归栈的深度最多为3层,使用的额外空间是常数级的 |