【11.2】数学思维-解两个盒子中球的颜色数相同的概率

目录

一、题目

二、解题思路

三、代码实现


 🎬 攻城狮7号个人主页

🔥 个人专栏: 《C/C++算法》

⛺️ 君子慎独!

 🌈 大家好,欢迎来访我的博客!
⛳️ 此篇文章主要讲解算法题目:解两个盒子中球的颜色数相同的概率
📚 本期文章收录在《C/C++算法》,大家有兴趣可以自行查看!
⛺️ 欢迎各位 ✔️ 点赞 👍 收藏 ⭐留言 📝!

一、题目

        桌面上有 2n 个颜色不完全相同的球,球的颜色共有 k 种。给你一个大小为 k 的整数数组 balls ,其中 balls[i] 是颜色为 i 的球的数量。

        所有的球都已经 随机打乱顺序 ,前 n 个球放入第一个盒子,后 n 个球放入另一个盒子(请认真阅读示例 2 的解释部分)。

        注意:这两个盒子是不同的。例如,两个球颜色分别为 ab,盒子分别为 [](),那么 [a] (b)[b] (a) 这两种分配方式是不同的(请认真阅读示例的解释部分)。

        请返回「两个盒子中球的颜色数相同」的情况的概率。答案与真实值误差在 10-5 以内,则被视为正确答案

示例 1:

输入:balls = [1,1]
输出:1.00000
解释:球平均分配的方式只有两种:
- 颜色为 1 的球放入第一个盒子,颜色为 2 的球放入第二个盒子
- 颜色为 2 的球放入第一个盒子,颜色为 1 的球放入第二个盒子
这两种分配,两个盒子中球的颜色数都相同。所以概率为 2/2 = 1 。

示例 2:

输入:balls = [2,1,1]
输出:0.66667
解释:球的列表为 [1, 1, 2, 3]
随机打乱,得到 12 种等概率的不同打乱方案,每种方案概率为 1/12 :
[1,1 / 2,3], [1,1 / 3,2], [1,2 / 1,3], [1,2 / 3,1], [1,3 / 1,2], [1,3 / 2,1], [2,1 / 1,3], [2,1 / 3,1], [2,3 / 1,1], [3,1 / 1,2], [3,1 / 2,1], [3,2 / 1,1]
然后,我们将前两个球放入第一个盒子,后两个球放入第二个盒子。
这 12 种可能的随机打乱方式中的 8 种满足「两个盒子中球的颜色数相同」。
概率 = 8/12 = 0.66667

示例 3:

输入:balls = [1,2,1,2]
输出:0.60000
解释:球的列表为 [1, 2, 2, 3, 4, 4]。要想显示所有 180 种随机打乱方案是很难的,但只检查「两个盒子中球的颜色数相同」的 108 种情况是比较容易的。
概率 = 108 / 180 = 0.6 。

提示:

  • 1 <= balls.length <= 8
  • 1 <= balls[i] <= 6
  • sum(balls) 是偶数

二、解题思路

        这个问题可以通过组合数学和概率论来解决。我们需要计算所有可能的分配方式中,满足两个盒子中球的颜色数相同的分配方式所占的比例。

1. **总分配方式**:首先,我们需要计算所有可能的分配方式的总数。由于有 `2n` 个球,其中每种颜色的球有 `balls[i]` 个,总分配方式数为所有球的多重排列数,推导过程如下:


   
2. **满足条件的分配方式**:我们需要计算满足两个盒子中球的颜色数相同的分配方式数。对于每种颜色 `i`,我们需要将其分配到两个盒子中,使得两个盒子中该颜色的球数相等(如果 `balls[i]` 是偶数)或者相差1(如果 `balls[i]` 是奇数)。对于每种颜色,我们可以计算其分配到两个盒子中的方式数,然后将所有颜色的方式数相乘,得到总的满足条件的分配方式数。

3. **概率计算**:最后,我们将满足条件的分配方式数除以总分配方式数,得到概率。

三、代码实现

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>

using namespace std;

// 计算阶乘
double factorial(int n) {
    double result = 1.0;
    for (int i = 2; i <= n; ++i) {
        result *= i; // 累乘计算阶乘
    }
    return result;
}

// 计算组合数 C(n, k)
double combination(int n, int k) {
    if (k > n - k) k = n - k; // 利用组合数的对称性 C(n, k) = C(n, n-k),减少计算量
    double result = 1.0;
    for (int i = 0; i < k; ++i) {
        result *= (n - i); // 计算分子 n * (n-1) * ... * (n-k+1)
        result /= (i + 1); // 计算分母 k!
    }
    return result;
}

// 计算满足条件的分配方式数
double countValidWays(const vector<int>& balls, int n, int k) {
    double validWays = 1.0;
    for (int i = 0; i < k; ++i) {
        int ballCount = balls[i]; // 当前颜色的球的数量
        if (ballCount % 2 == 0) {
            // 如果当前颜色的球数是偶数,则将其平分到两个盒子中
            validWays *= combination(ballCount, ballCount / 2);
        } else {
            // 如果当前颜色的球数是奇数,则有两种分配方式:
            // 1. 一个盒子比另一个盒子多一个球
            // 2. 另一个盒子比这个盒子多一个球
            validWays *= combination(ballCount, ballCount / 2) + combination(ballCount, ballCount / 2 + 1);
        }
    }
    return validWays;
}

// 计算总分配方式数
double totalWays(const vector<int>& balls, int n, int k) {
    double total = factorial(2 * n); // 计算所有球的总排列数 (2n)!
    for (int i = 0; i < k; ++i) {
        total /= factorial(balls[i]); // 除以每种颜色球的重复排列数 balls[i]!
    }
    return total;
}

// 计算概率
double calculateProbability(const vector<int>& balls) {
    int k = balls.size(); // 颜色的种类数
    int n = 0;
    for (int i = 0; i < k; ++i) {
        n += balls[i]; // 计算总球数
    }
    n /= 2; // 每个盒子中的球数

    // 计算满足条件的分配方式数
    double validWays = countValidWays(balls, n, k);
    // 计算总分配方式数
    double total = totalWays(balls, n, k);

    // 返回概率
    return validWays / total;
}

int main() {
    vector<int> balls = {2, 1, 1}; // 示例2的输入
    double probability = calculateProbability(balls); // 计算概率
    cout << "概率: " << probability << endl; // 输出结果

    return 0;
}

复杂度分析

- 时间复杂度:主要取决于阶乘和组合数的计算,复杂度为 `O(n)`,其中 `n` 是球的总数。
- 空间复杂度:主要是存储中间结果,复杂度为 `O(1)`。

看到这里了还不给博主点一个:
⛳️ 点赞☀️收藏 ⭐️ 关注

💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖
再次感谢大家的支持!
你们的点赞就是博主更新最大的动力!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城狮7号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值