错排公式推导
我们要计算 ( D_n ),即长度为 ( n ) 的排列中,所有元素都不在自己的原位置上的排列个数,这个问题称为错排问题(Derangement)。我们一步一步推导这个公式。
第一步:理解问题
我们需要计算一个排列的数量,其中对于 ( n ) 个数,每个数 ( i ) 都不能出现在位置 ( i ) 上。
例如:
- 对于 ( n = 3 ),所有排列是:只有 ( (2,3,1) ) 和 ( (3,1,2) ) 满足错排条件,所以 ( D_3 = 2 )。
第二步:递归思想
设 ( D_n ) 表示 ( n ) 个数的错排数。
我们考虑第一个数 ( 1 ) 放在哪里:
-
它不能放在位置 1,所以它可以放在 其他 ( n-1 ) 个位置中的某个位置 ( k )(( 2 \leq k \leq n ))。
-
现在,原本在位置 ( k ) 的数必须换个位置,我们有两种情况:
- 把它放回位置 1:这样剩下的 ( n-1 ) 个数要构成一个错排,即剩下的 ( n-1 ) 个数的错排问题,方案数是 ( D_{n-2} )。
- 不放回位置 1:那么它仍然是一个错排问题,只不过规模是 ( n-1 ),方案数是 ( D_{n-1} )。
因为 ( 1 ) 可以放在任意 ( n-1 ) 个位置上,所以总的递推关系是:
这个递推式是错排问题的核心。
第三步:边界条件
- 只有 1 个数时,没有错排,( D_1 = 0 )。
- 有 2 个数时,唯一的错排是 ( (2,1) ),所以 ( D_2 = 1 )。
即:
第四步:具体计算
使用递推公式计算几个小的值:
第五步:一般公式
从组合数学的研究中,我们可以推导出错排数的通项公式:
这个公式的直观解释是:我们先算出所有的排列数 ( n! ),然后用容斥原理逐步去掉那些有固定元素在原位的情况。
近似计算:
其中 ( e ) 是自然对数的底(约等于 2.718)。
第六步:代码实现
递归方式
#include <iostream>
using namespace std;
long long derangement(int n) {
if (n == 1) return 0;
if (n == 2) return 1;
return (n - 1) * (derangement(n - 1) + derangement(n - 2));
}
int main() {
int n;
cin >> n;
cout << derangement(n) << endl;
return 0;
}
动态规划方式
#include <iostream>
using namespace std;
long long dp[1000005];
int main() {
int n;
cin >> n;
dp[1] = 0;
dp[2] = 1;
for (int i = 3; i <= n; ++i) {
dp[i] = (i - 1) * (dp[i - 1] + dp[i - 2]);
}
cout << dp[n] << endl;
return 0;
}
时间复杂度:( O(n) ),空间复杂度:( O(n) )。
总结
- 递推关系是:
- 边界条件是:
- 计算公式:
- 代码可以用递归或者动态规划来求解。
这样,我们就从最基本的思考出发,一步步推导出了错排问题的解法啦!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战