Hard
题目描述
给你一个整数数组 balls,其中 balls[i] 是颜色为 i 的球的数量。
现在,你要将这些球放入两个盒子中,使得:
- 每个盒子中球的总数相同
- 求两个盒子中球的颜色数相同的概率
所有的球会被随机洗牌,然后前 n 个球放入第一个盒子,剩下的 n 个球放入第二个盒子。
请注意,两个盒子被认为是不同的。例如,如果我们有颜色为 a 和 b 的两个球,以及两个盒子 [] 和 (),那么分布 [a] (b) 与分布 [b] (a) 被认为是不同的。
返回两个盒子中球的颜色数相同的概率。在实际值的 10^-5 以内的答案将被接受。
示例 1:
输入:balls = [1,1]
输出:1.00000
解释:只有 2 种方法来平均分配球:
- 颜色 1 的球放到盒子 1,颜色 2 的球放到盒子 2
- 颜色 2 的球放到盒子 1,颜色 1 的球放到盒子 2
在两种方法中,每个盒子中不同颜色的数量都相等。概率是 2/2 = 1
示例 2:
输入:balls = [2,1,1]
输出:0.66667
解释:我们有球的集合 [1, 1, 2, 3]
示例 3:
输入:balls = [1,2,1,2]
输出:0.60000
约束:
- 1 <= balls.length <= 8
- 1 <= balls[i] <= 6
- sum(balls) 是偶数
解题思路
这是一个概率统计问题,需要使用回溯和组合数学来解决。
核心思路是通过回溯枚举所有可能的分配方案,然后计算满足条件的概率。
具体步骤:
- 回溯枚举:对于每种颜色的球,枚举分配到第一个盒子的数量(0到该颜色球的总数)
- 约束条件:确保两个盒子中的球数相等(各为总数的一半)
- 概率计算:使用多项式定理计算每种分配方案的概率
- 统计结果:累计所有满足"两个盒子颜色数相同"条件的方案概率
关键点:
- 使用组合数 C(n,k) = n!/(k!(n-k)!) 计算分配概率
- 每种分配方案的概率 = (各颜色组合数乘积) / C(总数, n)
- 需要预计算阶乘来快速计算组合数
- 回溯时需要剪枝:如果当前盒子球数已超过一半,直接返回
时间复杂度主要取决于回溯的分支数,在给定约束下是可行的。
代码实现
class Solution {
private:
vector<long long> factorial;
void calcFactorial(int n) {
factorial.resize(n + 1);
factorial[0] = 1;
for (int i = 1; i <= n; i++) {
factorial[i] = factorial[i - 1] * i;
}
}
long long combination(int n, int k) {
if (k > n || k < 0) return 0;
return factorial[n] / (factorial[k] * factorial[n - k]);
}
pair<double, double> dfs(vector<int>& balls, int index, vector<int>& box1, vector<int>& box2, int sum1, int sum2, int target) {
if (sum1 > target || sum2 > target) return {0, 0};
if (index == balls.size()) {
if (sum1 != target || sum2 != target) return {0, 0};
// 计算当前分配的概率
long long ways = 1;
int colors1 = 0, colors2 = 0;
for (int i = 0; i < balls.size(); i++) {
ways *= combination(balls[i], box1[i]);
if (box1[i] > 0) colors1++;
if (box2[i] > 0) colors2++;
}
double prob = (double)ways;
return {prob, colors1 == colors2 ? prob : 0};
}
double totalWays = 0, validWays = 0;
for (int i = 0; i <= balls[index]; i++) {
box1[index] = i;
box2[index] = balls[index] - i;
auto result = dfs(balls, index + 1, box1, box2, sum1 + i, sum2 + balls[index] - i, target);
totalWays += result.first;
validWays += result.second;
}
return {totalWays, validWays};
}
public:
double getProbability(vector<int>& balls) {
int totalBalls = 0;
for (int ball : balls) {
totalBalls += ball;
}
calcFactorial(totalBalls);
vector<int> box1(balls.size()), box2(balls.size());
auto result = dfs(balls, 0, box1, box2, 0, 0, totalBalls / 2);
return result.second / result.first;
}
};
class Solution:
def getProbability(self, balls: List[int]) -> float:
from math import factorial
def combination(n, k):
if k > n or k < 0:
return 0
return factorial(n) // (factorial(k) * factorial(n - k))
def dfs(index, box1, box2, sum1, sum2, target):
if sum1 > target or sum2 > target:
return 0, 0
if index == len(balls):
if sum1 != target or sum2 != target:
return 0, 0
# 计算当前分配的概率
ways = 1
colors1 = colors2 = 0
for i in range(len(balls)):
ways *= combination(balls[i], box1[i])
if box1[i] > 0:
colors1 += 1
if box2[i] > 0:
colors2 += 1
return ways, ways if colors1 == colors2 else 0
total_ways = valid_ways = 0
for i in range(balls[index] + 1):
box1[index] = i
box2[index] = balls[index] - i
total, valid = dfs(index + 1, box1, box2, sum1 + i, sum2 + balls[index] - i, target)
total_ways += total
valid_ways += valid
return total_ways, valid_ways
total_balls = sum(balls)
target = total_balls // 2
box1 = [0] * len(balls)
box2 = [0] * len(balls)
total_ways, valid_ways = dfs(0, box1, box2, 0, 0, target)
return valid_ways / total_ways
public class Solution {
private long[] factorial;
private void CalcFactorial(int n) {
factorial = new long[n + 1];
factorial[0] = 1;
for (int i = 1; i <= n; i++) {
factorial[i] = factorial[i - 1] * i;
}
}
private long Combination(int n, int k) {
if (k > n || k < 0) return 0;
return factorial[n] / (factorial[k] * factorial[n - k]);
}
private (double, double) DFS(int[] balls, int index, int[] box1, int[] box2, int sum1, int sum2, int target) {
if (sum1 > target || sum2 > target) return (0, 0);
if (index == balls.Length) {
if (sum1 != target || sum2 != target) return (0, 0);
long ways = 1;
int colors1 = 0, colors2 = 0;
for (int i = 0; i < balls.Length; i++) {
ways *= Combination(balls[i], box1[i]);
if (box1[i] > 0) colors1++;
if (box2[i] > 0) colors2++;
}
double prob = (double)ways;
return (prob, colors1 == colors2 ? prob : 0);
}
double totalWays = 0, validWays = 0;
for (int i = 0; i <= balls[index]; i++) {
box1[index] = i;
box2[index] = balls[index] - i;
var result = DFS(balls, index + 1, box1, box2, sum1 + i, sum2 + balls[index] - i, target);
totalWays += result.Item1;
validWays += result.Item2;
}
return (totalWays, validWays);
}
public double GetProbability(int[] balls) {
int totalBalls = balls.Sum();
CalcFactorial(totalBalls);
int[] box1 = new int[balls.Length];
int[] box2 = new int[balls.Length];
var result = DFS(balls, 0, box1, box2, 0, 0, totalBalls / 2);
return result.Item2 / result.Item1;
}
}
var getProbability = function(balls) {
const n = balls.reduce((sum, count) => sum + count, 0) / 2;
function factorial(num) {
let result = 1;
for (let i = 2; i <= num; i++) {
result *= i;
}
return result;
}
function multinomial(counts) {
const total = counts.reduce((sum, count) => sum + count, 0);
let result = factorial(total);
for (const count of counts) {
result /= factorial(count);
}
return result;
}
let validCount = 0;
let totalCount = 0;
function backtrack(index, box1, box2, count1, count2) {
if (count1 > n || count2 > n) return;
if (index === balls.length) {
if (count1 === n && count2 === n) {
const distinctColors1 = box1.filter(x => x > 0).length;
const distinctColors2 = box2.filter(x => x > 0).length;
const ways = multinomial(box1) * multinomial(box2);
totalCount += ways;
if (distinctColors1 === distinctColors2) {
validCount += ways;
}
}
return;
}
for (let i = 0; i <= balls[index]; i++) {
box1[index] = i;
box2[index] = balls[index] - i;
backtrack(index + 1, box1, box2, count1 + i, count2 + balls[index] - i);
}
}
backtrack(0, new Array(balls.length).fill(0), new Array(balls.length).fill(0), 0, 0);
return validCount / totalCount;
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(∏(balls[i] + 1)),回溯遍历所有可能的分配方案 |
| 空间复杂度 | O(k + 总球数),k为颜色数量,用于存储分配状态和阶乘数组 |