You are given a set of k
different colors of balls. The i
-th color has balls[i]
balls. All balls of the same color are indistinguishable, but balls of different colors are distinguishable.
Your task is to divide all the balls into two boxes such that:
Constraints:
1 <= balls.length <= 8
1 <= balls[i] <= 6
sum(balls) <= 48
At first glance, this problem appears to be a classic combinatorial partitioning problem. Since balls of the same color are indistinguishable, it's about distributing counts rather than permutations. The brute-force way is to try every possible way to split the balls and count the number of ways that result in both boxes having the same number of distinct colors.
However, as the number of colors and balls increases, the number of ways grows exponentially. Optimizing means avoiding recomputation by leveraging recursion, memoization, or combinatorial mathematics. We need to count the number of valid distributions (where the number of distinct colors in each box is equal) and divide it by the total number of ways to distribute the balls.
The key insight is to use recursion to try all possible splits for each color, keeping track of how many colors are present in each box, and use combinatorics to count arrangements efficiently.
Here’s how we solve the problem step by step:
C(balls[i], x)
where x
is the number of balls of color i
in box 1.
This approach ensures we only count unique distributions (not permutations), and efficiently explores all possibilities using recursion.
Example Input: balls = [2, 1]
(2 balls of color A, 1 ball of color B)
Total balls: 3. We want to split them into two boxes, each with at least one ball, and count the ways both boxes have the same number of distinct colors.
Let's enumerate all possible distributions:
Ways to choose balls:
Total possible arrangements:
Brute-force approach:
i
, try all possible splits (0 to balls[i])product(balls[i]+1)
k
colors and maximum n
balls per color, this is O((n+1)^k)
This problem is a classic example of combinatorial distribution with constraints. The elegant solution uses recursion to explore all possible ball splits, tracks the number of distinct colors in each box, and efficiently counts arrangements using binomial coefficients. By dividing the number of valid distributions by the total number of distributions, we compute the required probability. The approach leverages the small input constraints and combinatorial mathematics for an efficient and clear solution.
from math import comb
from functools import lru_cache
class Solution:
def getProbability(self, balls):
n = len(balls)
total = sum(balls)
half = total // 2
# Precompute factorials up to 48 for combinations
max_balls = sum(balls)
fact = [1]
for i in range(1, max_balls+1):
fact.append(fact[-1]*i)
def C(n, k):
return fact[n] // (fact[k]*fact[n-k])
total_ways = 0
valid_ways = 0
def dfs(i, box1, box2, color1, color2, ways):
nonlocal total_ways, valid_ways
if i == n:
if box1 == 0 or box2 == 0: return
if box1 != box2: return
if color1 == color2:
valid_ways += ways
total_ways += ways
return
for pick in range(balls[i]+1):
next_box1 = box1 + pick
next_box2 = box2 + (balls[i] - pick)
next_color1 = color1 + (1 if pick > 0 else 0)
next_color2 = color2 + (1 if balls[i] - pick > 0 else 0)
dfs(i+1, next_box1, next_box2, next_color1, next_color2, ways * C(balls[i], pick))
dfs(0, 0, 0, 0, 0, 1)
return valid_ways / total_ways
#include <vector>
#include <numeric>
#include <algorithm>
using namespace std;
class Solution {
public:
vector fact;
long long C(int n, int k) {
return fact[n] / (fact[k] * fact[n-k]);
}
double getProbability(vector<int>& balls) {
int n = balls.size();
int total = accumulate(balls.begin(), balls.end(), 0);
int max_balls = total;
fact = vector<long long>(max_balls+1, 1);
for (int i = 1; i <= max_balls; ++i)
fact[i] = fact[i-1] * i;
long long valid_ways = 0, total_ways = 0;
function<void(int,int,int,int,int,long long)> dfs = [&](int i, int box1, int box2, int color1, int color2, long long ways) {
if (i == n) {
if (box1 == 0 || box2 == 0) return;
if (box1 != box2) return;
if (color1 == color2)
valid_ways += ways;
total_ways += ways;
return;
}
for (int pick = 0; pick <= balls[i]; ++pick) {
int next_box1 = box1 + pick;
int next_box2 = box2 + (balls[i] - pick);
int next_color1 = color1 + (pick > 0 ? 1 : 0);
int next_color2 = color2 + ((balls[i] - pick) > 0 ? 1 : 0);
dfs(i+1, next_box1, next_box2, next_color1, next_color2, ways * C(balls[i], pick));
}
};
dfs(0, 0, 0, 0, 0, 1LL);
return (double)valid_ways / total_ways;
}
};
import java.util.*;
class Solution {
long[] fact;
long C(int n, int k) {
return fact[n] / (fact[k] * fact[n-k]);
}
double validWays = 0, totalWays = 0;
public double getProbability(int[] balls) {
int n = balls.length;
int total = 0;
for (int b : balls) total += b;
fact = new long[total+1];
fact[0] = 1;
for (int i = 1; i <= total; ++i)
fact[i] = fact[i-1] * i;
dfs(balls, 0, 0, 0, 0, 0, 1L);
return validWays / totalWays;
}
void dfs(int[] balls, int i, int box1, int box2, int color1, int color2, long ways) {
if (i == balls.length) {
if (box1 == 0 || box2 == 0) return;
if (box1 != box2) return;
if (color1 == color2)
validWays += ways;
totalWays += ways;
return;
}
for (int pick = 0; pick <= balls[i]; ++pick) {
int nextBox1 = box1 + pick;
int nextBox2 = box2 + (balls[i] - pick);
int nextColor1 = color1 + (pick > 0 ? 1 : 0);
int nextColor2 = color2 + ((balls[i] - pick) > 0 ? 1 : 0);
dfs(balls, i+1, nextBox1, nextBox2, nextColor1, nextColor2, ways * C(balls[i], pick));
}
}
}
var getProbability = function(balls) {
const n = balls.length;
const total = balls.reduce((a,b) => a+b, 0);
// Precompute factorials
let fact = [1];
for (let i = 1; i <= total; ++i)
fact[i] = fact[i-1] * i;
function C(n, k) {
return fact[n] / (fact[k]*fact[n-k]);
}
let validWays = 0, totalWays = 0;
function dfs(i, box1, box2, color1, color2, ways) {
if (i === n) {
if (box1 === 0 || box2 === 0) return;
if (box1 !== box2) return;
if (color1 === color2)
validWays += ways;
totalWays += ways;
return;
}
for (let pick = 0; pick <= balls[i]; ++pick) {
let nextBox1 = box1 + pick;
let nextBox2 = box2 + (balls[i] - pick);
let nextColor1 = color1 + (pick > 0 ? 1 : 0);
let nextColor2 = color2 + ((balls[i] - pick) > 0 ? 1 : 0);
dfs(i+1, nextBox1, nextBox2, nextColor1, nextColor2, ways * C(balls[i], pick));
}
}
dfs(0, 0, 0, 0, 0, 1);
return validWays / totalWays;
};