問題描述
有 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];
}