傳說這是愛因斯坦出的智力測驗,號稱全球僅 2% 人能解出。題目描述一條街上有 5 棟顏色不同的房子,每棟住不同國籍的人,他們飲品、菸牌、寵物皆不相同。給定 15 條線索(如「英國人住紅房子」、「綠房子在白房子左側」、「挪威人住第一間」等),推導出誰養魚。
每類屬性(國籍、顏色、寵物、飲料、菸牌)皆為 5 個元素的排列,共有 5! = 120 種排列。五類屬性獨立排列,總組合數高達 120^5 ≈ 2.49×10^10(約 249 億種)。純暴力窮舉在當時硬體上跑 30 分鐘仍無解,預估需數天才能完成——這是典型的組合爆炸問題。
核心思想:在生成排列過程中即時驗證約束,一旦不滿足立即跳過後續組合(類似邏輯運算中的短路求值)。具體步驟:
aid[120,5]。continue 跳過內層迴圈。enum 國籍 { 英國, 瑞典, 丹麥, 挪威, 德國 };
enum 顏色 { 紅, 綠, 藍, 黃, 白 };
enum 寵物 { 鳥, 貓, 馬, 魚, 狗 };
enum 飲料 { 水, 牛奶, 咖啡, 茶, 啤酒 };
enum 香煙 { blends, blue, prince, dunhill, pall };
// 5x5 矩陣:行=房子(0-4),列=屬性類別(國籍,顏色,寵物,飲料,香煙)
private int[,] array = new int[5, 5];
// 規則 4:綠房子在白房子左側(相鄰)
private bool assert4() {
return get顏色(顏色.綠) == get顏色(顏色.白) - 1;
}
// 規則 10:抽 Blends 的人住在養貓的人隔壁
private bool assert10() {
int t1 = get香煙(香煙.blends), t2 = get寵物(寵物.貓);
return Math.Abs(t1 - t2) == 1;
}
public void Solve() {
int sn = 1;
// 依約束強度排序:國籍 -> 飲料 -> 顏色 -> 寵物 -> 香煙
for (int i0 = 0; i0 < 120; i0++) { replace((int)T.國籍, i0); if (!assert9()) continue;
for (int i1 = 0; i1 < 120; i1++) { replace((int)T.飲料, i1); if (!assert8()) continue; if (!assert3()) continue;
for (int i2 = 0; i2 < 120; i2++) { replace((int)T.顏色, i2); if (!assert4()) continue; if (!(assert1() && assert14() && assert5())) continue;
for (int i3 = 0; i3 < 120; i3++) { replace((int)T.寵物, i3); if (!assert2()) continue;
for (int i4 = 0; i4 < 120; i4++) { replace((int)T.香煙, i4);
if (assert6() && assert7() && assert10() && assert11() && assert12() && assert15() && assert13()) {
if (assert()) { Console.WriteLine("解:" + sn++); Console.WriteLine(this); }
}
}
}
}
}
}
}
程式輸出唯一解:德國人養魚。完整配置如下:
| 屋號 | 國籍 | 顏色 | 寵物 | 飲料 | 香煙 |
|---|---|---|---|---|---|
| 1 | 挪威 | 黃 | 貓 | 水 | dunhill |
| 2 | 丹麥 | 藍 | 馬 | 茶 | blends |
| 3 | 英國 | 紅 | 鳥 | 牛奶 | pall |
| 4 | 德國 | 綠 | 魚 | 咖啡 | prince |
| 5 | 瑞典 | 白 | 狗 | 啤酒 | blue |
同一問題,純暴力需天級耗時,加入短路剪枝後毫秒級出解。這深刻說明:好的算法不只是快,更是將指數級複雜度降為多項式級甚至常數級的關鍵。實務開發中,面對組合優化、約束滿足類問題(如排程、路徑規劃),務必優先考慮剪枝、回溯、動態規劃等策略,而非盲目窮舉。