演算法系列 · 第 19 關 · 遞迴與進階

回溯 Backtracking

選一步、走下去、走不通就撤回。用 DFS 系統化地試遍所有可能,還會提早剪枝。

回溯是「系統化的試誤」:面對一連串選擇,選一個往下走,一旦發現這條路不可能成功就撤回上一步(backtrack),換下一個選項。它本質是在「決策樹」上做 DFS,加上兩個關鍵:撤銷(undo)和剪枝(pruning) —— 提早砍掉不可能的分支,才不會真的試遍天文數字種組合。

回溯的骨架:選 → 遞迴 → 撤銷

選一步 —— 從目前能做的選項挑一個,做下去(記錄這個選擇)。

往下遞迴 —— 在「做了這個選擇」的新狀態下,繼續解剩下的。

撤銷(backtrack) —— 若這條路失敗,把剛剛的選擇收回,試下一個選項。

跟純 DFS 差在哪 —— 就是多了「撤銷」那一步,把狀態還原,才能乾淨地試下一條路。

剪枝:回溯為什麼沒想像中慢

暴力法會試「所有」組合 —— 例如 N 皇后有 nⁿ 種擺法,天文數字。

回溯會提早放棄 —— 只要目前的部分擺法已經違規(兩皇后互攻),就不再往下,整條分支砍掉。

這叫剪枝(pruning) —— 越早發現不可行、砍掉越多,實際跑起來越快。

好的剪枝是效率關鍵 —— 同一個問題,剪得好可以差好幾個數量級(數獨、N 皇后都靠它秒解)。

複雜度(通常指數級)

O(n!)排列類 —— 全排列、N 皇后:每一步的選項一直在變少,共 n! 條路。
O(2ⁿ)子集 / 組合類 —— 每個元素「選或不選」,共 2ⁿ 種。
O(n)空間 —— 遞迴深度 + 目前的部分解,通常只有 n 這麼深。
一句話:回溯 = DFS + 撤銷 + 剪枝。它保證找到所有解(或第一個解),代價是最壞指數級;但靠剪枝,很多實際問題(數獨、N 皇后)其實秒解。

用生活比喻它

🧩

拼拼圖

試一片,不合就拿起來換下一片(撤銷),合了才繼續拼旁邊。

🗝️

試鑰匙開多道門

每道門試一把,開了往前走;走到死路就退回上一道門換路線。

✏️

鉛筆解數獨

填一個數,矛盾就擦掉重填(撤銷),一格一格逼近答案。

🎮 四皇后的回溯

在 4×4 棋盤放 4 個皇后,讓它們互不攻擊(同行、同列、同對角線都算)。回溯一列一列試:能放就放(紫色放下、綠色已放),琥珀是被現有皇后攻擊、不能放的格子;整列都放不了就回溯收回上一個。按「下一步」。

10 個程式碼範例(Python)

回溯的招牌題:全排列、子集、組合、N 皇后、括號生成、數獨、字母組合、單字搜尋,最後給通用模板。

顯示範例:

常見陷阱

❌ 忘了撤銷 —— 選了沒收回,狀態會髒掉、影響其他分支,答案全錯。

❌ 收集解時沒複製 —— result.append(path) 存的是同一個 list 的參照,之後會被改掉;要 path[:] 或 list(path)。

❌ 沒有剪枝 —— 能提早判斷不可行卻不剪,會慢到跑不完。

❌ 組合 / 子集沒用 start 去重 —— 要用 start 只往後選,不然會冒出重複或變成排列。

🎯 換你了(先別急著查答案)

把答案打到對話裡,我幫你對。