回溯經典問題#

N 皇后問題(N 皇后 / N 皇后 II #

在 N×N 棋盤上放置 N 個皇后,使它們互不攻擊。

攻擊範圍#

皇后可攻擊:橫向(行)、縱向(列)、斜向(撇)、反斜向(捺)

每一行只能有一個皇后,因此可以將「行」視為遞迴深度,只需決定每行皇后放在哪一列。

快速判斷合法性#

用三個集合記錄「被攻擊的區域」,判斷時間降為 O(1)

集合記錄內容數學特性
cols被佔用的列col
pie (撇 /)左下到右上斜線row + col = 常數
na (捺 \)左上到右下斜線row - col = 常數

程式碼框架#

fun solveNQueens(n: Int): List<List<Int>> {
    val result = mutableListOf<List<Int>>()

    fun dfs(row: Int, cols: Set<Int>, pie: Set<Int>, na: Set<Int>, path: List<Int>) {
        if (row >= n) {
            result.add(path)
            return
        }

        for (col in 0 until n) {
            // 剪枝:檢查是否衝突
            if (col in cols || (row + col) in pie || (row - col) in na) continue

            // 遞迴(傳入新的集合狀態,用 + 產生新集合、自動「回溯」)
            dfs(
                row + 1,
                cols + col,
                pie + (row + col),
                na + (row - col),
                path + col,
            )
        }
    }

    dfs(0, emptySet(), emptySet(), emptySet(), emptyList())
    return result
}

若使用全域集合,必須在遞迴返回後手動移除剛加入的元素(回溯)。

複雜度#

  • 時間O(N!)
  • 空間O(N)

數獨問題(有效的數獨 / 解數獨 #

數獨規則#

在 9×9 格子填入 1-9,滿足:

  1. 1-9 各出現一次
  2. 1-9 各出現一次
  3. 每個 3×3 九宮格 1-9 各出現一次

驗證數獨 #

檢查當前盤面是否合法(不需判斷有解)。

解法:走訪 9×9 盤面,用三組陣列/集合分別記錄行、列、九宮格的數字出現情況。

九宮格座標映射:格子 (i, j) 對應的九宮格索引為 (i/3, j/3)

解數獨 #

填滿空格,找到一個解。

回溯解法

  1. 找到第一個空格
  2. 嘗試填入 1-9
  3. 檢查合法性
  4. 合法則遞迴處理下一個空格
  5. 失敗則撤銷(回溯),嘗試下一個數字
程式碼實作
fun solveSudoku(board: Array<CharArray>) {
    fun isValid(row: Int, col: Int, num: Char): Boolean {
        for (i in 0 until 9) {
            // 檢查行與列
            if (board[row][i] == num) return false
            if (board[i][col] == num) return false
        }
        // 檢查九宮格
        val boxRow = 3 * (row / 3)
        val boxCol = 3 * (col / 3)
        for (i in boxRow until boxRow + 3) {
            for (j in boxCol until boxCol + 3) {
                if (board[i][j] == num) return false
            }
        }
        return true
    }

    fun solve(): Boolean {
        for (i in 0 until 9) {
            for (j in 0 until 9) {
                if (board[i][j] == '.') {
                    for (num in '1'..'9') {
                        if (isValid(i, j, num)) {
                            board[i][j] = num
                            if (solve()) return true
                            board[i][j] = '.'  // 回溯
                        }
                    }
                    return false  // 1-9 都不行,回溯
                }
            }
        }
        return true  // 填滿了
    }

    solve()
}

最佳化策略#

最佳化方法說明
MRV(最少候選值優先)優先填「可選數字最少」的格子
預處理先計算每個空格的候選數字
位運算用 bitmask 表示數字佔用情況

遞迴函式必須有明確的終止條件。當走訪完整個棋盤且沒有返回 false 時,代表找到解,應立即 return true 終止所有遞迴。


問題對比#

問題搜尋維度每步選擇數剪枝重點
N 皇后逐行放置N 列行列斜線衝突
數獨逐格填寫9 個數字行列九宮格衝突
括號生成逐字元選擇2 種括號括號數量約束

這些問題的共同點:都是在解空間樹上進行 DFS,透過約束條件剪枝。