跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

LeetCode 1227 小鸟回笼与飞机座位问题概率分析

综述由AI生成讨论 LeetCode 1227 小鸟回笼问题及经典飞机座位问题的概率计算。通过分析第 k 只鸟选择笼子的情况,推导出最后一只鸟回到自己笼子的概率公式。对比发现,当第一只鸟不选自己笼子时,概率为 (n-2)/(2(n-1));若第一只鸟随机选座,则概率为 1/2。文章提供 C++ 代码进行蒙特卡洛模拟验证,并指出了忽略事件依赖关系的常见错误解法。

接口猎人发布于 2026/3/23更新于 2026/5/616K 浏览

题目

有 n 只小鸟,各有自己的笼子(编号 1, 2, ..., n)。第一天,第一只小鸟(编号 1)没有回到自己的笼子(笼 1),而是随机进了其它某个笼子。后续的小鸟每天回来时,如果自己的笼子空着就进自己的笼子,否则从剩下的空笼子中随机选一个。

问:最后一只回笼的小鸟回到自己笼子的概率是多少?

这个问题和经典的飞机座位问题等价(见下),但需要注意的时初始条件不同,下面的问题第一个人位置也是随机的(可能回到自己的位置),而上面小鸟回笼问题则是在没有回到自己的笼子情况下。

有 n 位乘客即将登机,飞机正好有 n 个座位。第一位乘客的票丢了,他随便选了一个座位坐下。剩下的乘客将会:如果他们自己的座位还空着,就坐到自己的座位上;当他们自己的座位被占用时,随机选择其他座位,问:

第 n 位乘客坐在自己的座位上的概率是多少?

解答

**解:**设当所有鸟回到笼子后鸟和笼子编号的映射为 f(n) = m,其中 n 为鸟的编号,m 为笼子编号。

显然:

  • 当 n=1 时,第 1 只鸟即为最后一只鸟,因此其回到自己的鸟笼的概率为 P=1;
  • 当 n=2 时,第 1 只鸟因为没有回到自己的笼子,必然选择了第 2 只鸟即最后一只回笼小鸟所属的笼子,因此最后一只回笼小鸟回到自己的笼子的概率为 P=0。

下面讨论当 n≥3 时的情况,显然:

  • f(1) ≠ 1;
  • 若最后一只鸟回到自己的笼子,有 f(n)=n,否则 f(n)≠n。

再设当第 i 只鸟回到笼子时(1<i<n),剩下还没有鸟回笼的笼子编号集合为 C^l_i,剩下的鸟所属的笼子编号的集合为 C^b_i = {i, i+1, ..., n}。设 f(1)=k, k≠1。

若 k=n,则第 n 只鸟必然无法回到自己的笼子;当 k≠n 时,考虑第 k 只鸟回笼时,会发生如下情况:

  • f(j)=j, 1<j<k;
  • C^l_k={1, k+1, k+2, ..., n},共 n-k+1 项。

显然,这一系列事件发生概率是由 f(1)=k 直接导致的,设此事件发生的概率为 P_k = 1/(n-1),此时第 k 只鸟必须从 C^l_k={1, k+1, ..., n} 中随机选择一个笼子回笼,会出现三种情况:

  • 选择笼子 1 回笼,则从 i>k 开始,第 i 只鸟的笼子都没有被占,后续所有鸟都会回到自己的笼子,这个事件发生的概率为 P^1_k = P_k * 1/(n-i+1);
  • 选择笼子 n 回笼,则第 n 只鸟因为自己的笼子已被占,因此必然无法回到自己的笼子,这个事件发生的概率也为 P^n_k = P_k * 1/(n-i+1);
  • 选择笼子 k+l 回笼,则当第 k+l 只鸟回笼时,C^l_{k+l}={1, k+l+1, k+l+2, ..., n},显然第 k+l 只鸟回笼选择笼子的情况和第 k 只鸟相同,发生了一个递归过程。

这里可以定性的分析出,不论在哪个递归的分支,都有 P^1=P^n,即当笼子被占用的鸟随机选择笼子时,选择笼子 1 和 n 会直接决定第 n 只鸟能否回到自己的笼子,而这两种结果的概率是永远相等的。因此当 f(1)=k, k≠1, k≠n 时,第 n 只鸟能回到自己笼子的概率为 P^s_k = 1/2 * P_k,利用全概率公式,最终概率为 P = ∑_{k=2}^{n-2} 1/2 * P_k = (n-2)/(2(n-1))

综上,最后一只鸟回到自己的鸟笼的概率为 P = { 1, & n=1 0, & n=2 (n-2)/(2(n-1)), & n≥3 }

和经典问题的区别

在飞机座位问题中,由于第一个人也是随机选择的,迭代可以从第一个乘客开始,因此最终答案为 P=1/2

进一步地分析,如果我们从第一只鸟开始迭代(即第一只鸟是随机选择自己的鸟笼的),有如下情况:

  • 显然当 n=1,P=1;
  • 显然当 n=2,P=1/2;
  • 显然当 n≥3,有:
    • 若 f(1)=n,则最后一只鸟必然无法回到自己的笼子中;
    • 若 f(1)=1,则后续回笼的鸟都能回到自己的笼子;
    • 设总共有 n 只鸟时,最后一只鸟能回到自己鸟笼的事件为 A_n,若 f(1)=k, 1<k<n,则当第 k 只鸟进行选择时,其实际上面对的是一个 n-k+1 规模的同样的问题,因此有

P(A_n) = P[f(1)=1] + P[f(1)≠1,n] * ∑{k=2}^{n-1} P(A{n-k+1}) = 1/n + 1/n * ∑{k=2}^{n-1} P(A{n-k+1})

可以递推出 P(A_2)=P(A_3)=1/2,用数学归纳法,假设 n≥3 时,P(A_n)=1/2,则 P(A_{n+1}) = 1/(n+1) + 1/(n+1) * (n-1) * 1/2 = 1/2

这和经典问题是相同的。综上, P(A_n) = { 1, & n=1 1/2, & n≥2 }

小鸟回笼问题事实上是第一只鸟没有选择笼子 1 的条件 B 下(对应地第一只鸟选择笼子 1 的条件为 B̄),事件 A_n 发生的概率 P(A_n|B)。根据上述讨论结果,显然 P(A_n B̄) = 1/n

最后一只鸟能回到自己笼子事件 A 发生的概率,其中 P(A_n B) = P(A_n) - P(A_n B̄) = 1/2 - 1/n = (n-2)/(2n)

则 P(A_n|B) = P(A_n B)/P(B) = ((n-2)/(2n)) / ((n-1)/n) = (n-2)/(2(n-1))

因此两个问题本质是相同的,只是小鸟回笼问题多了一个条件,在这个条件下,最后一只鸟回到自己的笼子的概率就发生变化了。

代码验证

本题小鸟回笼 C++ 验证代码如下:

#include <bits/stdc++.h>
using namespace std;

static bool one_trial(int n, mt19937 &rng) {
    vector<int> occupied(n + 1, 0);
    vector<int> empty; empty.reserve(n);
    for (int c = 1; c <= n; ++c) empty.push_back(c);
    vector<int> pos(n + 1, -1);
    for (int i = 0; i < n; ++i) pos[empty[i]] = i;

    auto erase_empty = [&](int cage) {
        int idx = pos[cage];
        int last = empty.back();
        empty[idx] = last; pos[last] = idx;
        empty.pop_back(); pos[cage] = -1;
    };

    auto occupy = [&](int bird, int cage) {
        occupied[cage] = bird;
        erase_empty(cage);
    };

    auto pick_from_empty = [&](int m) -> int {
        uniform_int_distribution<int> dist(0, m - 1);
        return empty[dist(rng)];
    };

    if (n == 1) { occupy(1, 1); return true; }

    uniform_int_distribution<int> dist_first(2, n);
    int cage1 = dist_first(rng);
    occupy(1, cage1);

    for (int bird = 2; bird <= n; ++bird) {
        if (occupied[bird] == 0) {
            occupy(bird, bird);
        } else {
            int cage = pick_from_empty((int)empty.size());
            occupy(bird, cage);
        }
    }
    return occupied[n] == n;
}

static double simulate(int n, int trials, uint32_t seed) {
    mt19937 rng(seed);
    int success = 0;
    for (int t = 0; t < trials; ++t) {
        if (one_trial(n, rng)) success++;
    }
    return (double)success / trials;
}

static double theory(int n) {
    if (n == 1) return 1.0;
    if (n == 2) return 0.0;
    return (n - 2) / (2.0 * (n - 1));
}

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    vector<int> ns = {1, 2, 3, 4, 5, 10, 20, 50, 100};
    int trials = 200000;
    uint32_t seed = 42;
    cout << "Trials per n = " << trials << "\n";
    for (int n : ns) {
        double est = simulate(n, trials, seed);
        double th = theory(n);
        cout << "n=" << setw(3) << n << " est=" << fixed << setprecision(5) << est 
             << " theory=" << fixed << setprecision(5) << th << "\n";
    }
    return 0;
}

经典问题验证代码如下:

#include <bits/stdc++.h>
using namespace std;

bool trial(int n, mt19937 &rng) {
    vector<int> occupied(n + 1, 0);
    vector<int> empty; empty.reserve(n);
    for (int c = 1; c <= n; ++c) empty.push_back(c);
    vector<int> pos(n + 1, -1);
    for (int i = 0; i < n; ++i) pos[empty[i]] = i;

    auto occupy = [&](int bird, int cage) {
        occupied[cage] = bird;
        int idx = pos[cage];
        int last = empty.back();
        empty[idx] = last; pos[last] = idx;
        empty.pop_back(); pos[cage] = -1;
    };

    auto choice_index = [&](int m) {
        uniform_int_distribution<int> dist(0, m - 1);
        return dist(rng);
    };

    int cage1 = empty[choice_index((int)empty.size())];
    occupy(1, cage1);

    for (int bird = 2; bird <= n; ++bird) {
        if (occupied[bird] == 0) {
            occupy(bird, bird);
        } else {
            int cage = empty[choice_index((int)empty.size())];
            occupy(bird, cage);
        }
    }
    return occupied[n] == n;
}

double simulate(int n, int T, uint32_t seed = 42) {
    mt19937 rng(seed);
    int success = 0;
    for (int t = 0; t < T; ++t) {
        if (trial(n, rng)) success++;
    }
    return (double)success / T;
}

int main() {
    vector<int> ns = {1, 2, 3, 4, 5, 10, 50, 100};
    int T = 200000;
    for (int n : ns) {
        double est = simulate(n, T);
        cout << "n=" << setw(3) << n << ", estimated P=" << fixed << setprecision(4) << est << "\n";
    }
}

常见误区

以下是一种常见的错误解法,主要问题在于遗漏了'如果自己的笼子空着就进自己的笼子'这一条件,因此独立地计算了每只鸟回笼的概率,实际上编号靠前的鸟回笼的选择会影响后续鸟的策略,因此各个事件不是相互独立的。

**解:**显然 n=1 时,第一只鸟就是最后一只鸟,其回到自己笼子的概率为 P=1。

最后一只鸟能回到鸟笼的情况需要以下两个条件:

  1. 第一只鸟在没有进入自己鸟笼的前提下没有进到最后一只鸟的鸟笼中;
  2. 剩下的鸟都没有进到最后一只鸟的鸟笼中。

n=2 时,第一只鸟没有回到自己的笼子,必然进入了最后一只鸟的笼子,因此最后一只鸟回到自己笼子的概率为 P=0。

n>2 时,对于第一个条件,有一个前提是第一只鸟没有进入自己的鸟笼,因此其是在剩下的 n-1 个鸟笼中随机选择一个,其不选到最后一只鸟的概率为: P_1 = (n-2)/(n-1)

对于第二个条件,即前面的鸟都没有选到最后一个鸟笼 P_2 = (n-2)/(n-1) * (n-3)/(n-2) * ... * 1/2 = 1/(n-1)

因此,最终的概率为 P = P_1 * P_2 = (n-2)/(n-1) * 1/(n-1) = (n-2)/(n-1)^2

综上,最后一只鸟回到自己笼子的概率为: P = { 1, & n=1 (n-2)/(n-1)^2, & n>1 }

目录

  1. 题目
  2. 解答
  3. 和经典问题的区别
  4. 代码验证
  5. 常见误区
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Microsoft Edge WebView2 Runtime 官方安装与故障排查指南
  • Spring AI Alibaba 与 AgentScope 框架选型指南
  • C++ 递归算法实战:汉诺塔问题详解
  • 位图矢量化技术瓶颈突破:Potrace 算法深度解析与应用实践
  • Linux 信号处理:可重入函数原理与实战规范
  • 波士顿动力机器人技术解析:从 Spot 到 Atlas 的演进与商业化
  • FPGA 千兆以太网 SGMII 接口配置实战
  • C++ 模拟实现二叉搜索树
  • Go2 机器人强化学习(RL)开发实操指南
  • Python 语法速览与实战清单
  • 零基础学会逻辑回归:从原理到实现
  • 前后端分离与不分离架构对比分析
  • Elasticsearch + Kibana 实战指南:从安装部署到 C++ 客户端封装
  • 大模型训练原理深度解析:以 GPT-1 为例
  • Python 入门指南:学历并非门槛,零基础也能掌握技术
  • 阿里开源 Page-Agent:一行代码实现浏览器内 AI 原生应用
  • 具身导航 VLN 前沿论文汇总(2023-2026)
  • Ubuntu Server 24.04.3 LTS 安装教程
  • Stable Diffusion 安装与常见问题解决(Mac 版)
  • CC-Switch:AI 编码助手配置管理工具

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online