回溯經典問題#

一、N 皇后問題(LeetCode 51/52)#

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

攻擊範圍#

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

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

快速判斷合法性#

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

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

程式碼框架#

def solveNQueens(n):
    result = []

    def dfs(row, cols, pie, na, path):
        if row >= n:
            result.append(path)
            return

        for col in range(n):
            # 剪枝:檢查是否衝突
            if col in cols or (row + col) in pie or (row - col) in na:
                continue

            # 遞迴(傳入新的集合狀態)
            dfs(row + 1,
                cols | {col},
                pie | {row + col},
                na | {row - col},
                path + [col])

    dfs(0, set(), set(), set(), [])
    return result

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

複雜度#

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

二、數獨問題(LeetCode 36/37)#

數獨規則#

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

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

LeetCode 36:驗證數獨#

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

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

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

LeetCode 37:解數獨#

填滿空格,找到一個解。

回溯解法

  1. 找到第一個空格
  2. 嘗試填入 1-9
  3. 檢查合法性
  4. 合法則遞迴處理下一個空格
  5. 失敗則撤銷(回溯),嘗試下一個數字
程式碼實作
def solveSudoku(board):
    def isValid(row, col, num):
        # 檢查行
        if num in board[row]:
            return False
        # 檢查列
        if num in [board[i][col] for i in range(9)]:
            return False
        # 檢查九宮格
        box_row, box_col = 3 * (row // 3), 3 * (col // 3)
        for i in range(box_row, box_row + 3):
            for j in range(box_col, box_col + 3):
                if board[i][j] == num:
                    return False
        return True

    def solve():
        for i in range(9):
            for j in range(9):
                if board[i][j] == '.':
                    for num in '123456789':
                        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,透過約束條件剪枝。