信件错排问题分析(动态规划)
问题描述
有 N 个信件和信箱,每封信件对应一个正确信箱位置。现在它们被打乱,求错误装信方式的数量。保证每一封信都装在错误的位置。
思路
抽象成动态规划问题
定义一个数组dp[]存储错误方式数量。dp[i]表示,有i封信、i个信箱情况下的错误装信方法总数。
转移方程建立
对于第N封信而言,假设其装在了第 K 个信箱中,对于第 K 封信,有两种情况,(1)信件 K 装在信箱 N 中;(2)信件 K 未被装在信箱 N 中。
1. 信件K装在信箱N中
如下图所示:
我们不看 K 和 N 两对信件和信箱,只关注剩下 N-2 对信件和信箱,有 dp[N-2] 种错误装信方法。同时, K 的取值范围: 1~N-1 ,因此共有 (N-1)*dp[N-2] 种错误装信方法。
2. 信件 K 未被装在信箱 N 中
如下图所示:
先给出结果,该情况的错误装信方法有 (N-1)*dp[N-1] 种。这个 dp[N-1] 是如何得出来,我思考了良久。
我们把问题重新描述一下,思路就会清晰很多。事实上,对于信件 i 来说,信箱 i 是它的“专属信箱”,每个信件都不能放入自己的专属信箱,对于n 个信件而言,都需要找其它 n-1 个信箱放入,其错排方法数为 dp[n] 。
问题的症结在于,对于上图中的信件 K 来说,其专属信箱,即 K 信箱已经被占用。
但是,我们可以把信箱 N 当做信件 K 的“专属信箱”,因为本情况下,信件 K 也不能放入信箱 N 。所以可以理解成求 N-1 封信件和 N-1 个信箱(除去信件 N)之间的错排数量问题。所以得到 dp[N-1]。
同理 K 的取值范围: 1~N-1 ,因此共有 (N-1)*dp[N-1] 种错误装信方法。
综上,状态转移方程:
代码
/**
* 动态规划-信件错排问题
*/
private int MailMisalignment(int n){
if(n==0) return 0;
if(n==1) return 0;
int[] dp = new int[n];
dp[0] = 0;
dp[1] = 1;
for(int i=2;i<n;++i){
dp[i] = (i-1)*dp[i-2] + (i-1)*dp[i-1];
}
return dp[n-1];
}