🏰 N-Queens問題解析

ビット操作による高速化アルゴリズムの詳細解説

💡 問題の概要

N-Queens問題は、n×nのチェスボード上にn個のクイーンを配置し、どのクイーンも他のクイーンを攻撃できない状態にする問題です。クイーンは縦・横・斜めに移動できるため、同じ行、列、対角線上に複数のクイーンを配置することはできません。

4×4ボードでの解の例

解1

解2

🚀 解法コード(ビット操作最適化版)

Python - N-Queens Solution with Bit Manipulation
class Solution:
    def totalNQueens(self, n: int) -> int:
        """
        N-Queens問題の解の数を求めるメソッド
        
        Args:
            n: チェスボードのサイズ (n x n)
            
        Returns:
            N-Queens問題の解の総数
        """
        def backtrack(row: int, cols: int, diag1: int, diag2: int) -> int:
            """
            バックトラッキングを用いてN-Queens問題を解く再帰関数
            
            Args:
                row: 現在処理中の行
                cols: 列の使用状況をビットマスクで表現
                diag1: 左上から右下への対角線の使用状況をビットマスクで表現
                diag2: 右上から左下への対角線の使用状況をビットマスクで表現
                
            Returns:
                現在の状態から可能な解の数
            """
            # 全ての行にクイーンを配置できた場合、解を1つカウント
            if row == n:
                return 1
                
            count: int = 0
            # 現在の行で使用可能な位置を計算(ビット演算で高速化)
            available_positions: int = ((1 << n) - 1) & ~(cols | diag1 | diag2)
            
            # 使用可能な各位置にクイーンを配置して再帰的に探索
            while available_positions:
                # 最下位の1ビットを取得(次に配置可能な位置)
                position: int = available_positions & -available_positions
                # その位置のビットをクリア
                available_positions ^= position
                
                # 次の行で再帰的に探索し、解の数を加算
                # diag1とdiag2は対角線の制約を表現(ビットシフトで位置調整)
                count += backtrack(
                    row + 1,
                    cols | position,           # 列の制約を更新
                    (diag1 | position) << 1,  # 左上-右下対角線の制約を更新
                    (diag2 | position) >> 1   # 右上-左下対角線の制約を更新
                )
                
            return count
            
        # 最初の行から探索開始
        return backtrack(0, 0, 0, 0)

🔧 ビット操作の詳細解析

ビットマスクによる制約表現

このアルゴリズムの核心は、クイーンの配置制約をビットマスクで効率的に表現することです。

cols(列の制約): どの列にクイーンが配置されているかを記録
diag1(左上-右下対角線): 各対角線の使用状況を記録(左シフトで位置調整)
diag2(右上-左下対角線): 各対角線の使用状況を記録(右シフトで位置調整)

4×4ボードでのビット表現例(行2でクイーンを配置する場合)

現在の行: row = 2
列制約 (cols): 0010 ← 1列目にクイーン配置済み
対角線1 (diag1): 1000 ← 左上-右下対角線制約
対角線2 (diag2): 0001 ← 右上-左下対角線制約
全制約 OR: 1011 ← 使用不可位置
利用可能位置: 0100 ← ~(cols | diag1 | diag2) & ((1<<4)-1)< /span>

🎯 核心的なビット演算テクニック

Key Bit Manipulation Techniques
# 1. 利用可能な位置の計算
available_positions = ((1 << n) - 1) & ~(cols | diag1 | diag2)
# ((1 << n) - 1): n個の1からなるマスク (例: n=4 → 1111)
# ~(cols | diag1 | diag2): 制約のある位置を反転
# &: 有効範囲内での利用可能位置

# 2. 最下位ビットの取得
position = available_positions & -available_positions
# -available_positions: 2の補数(ビット反転+1)
# &演算で最下位の1ビットのみを抽出

# 3. ビットのクリア
available_positions ^= position
# XOR演算で該当ビットを0にセット

# 4. 対角線制約の更新
(diag1 | position) << 1  # 左上-右下: 左シフトで次行の制約
(diag2 | position) >> 1  # 右上-左下: 右シフトで次行の制約

最下位ビット抽出の仕組み

available_positions: 1100
-available_positions: 0100 ← 2の補数
& 演算結果: 0100 ← 最下位の1ビットのみ

📊 アルゴリズムの実行フロー

Step 1: 初期化
backtrack(0, 0, 0, 0) - 最初の行から開始
Step 2: 終了条件チェック
row == n なら解を1つカウントして終了
Step 3: 利用可能位置計算
ビット演算で配置可能な位置を高速計算
Step 4: 各位置を試行
最下位ビットから順番に各位置でクイーンを配置
Step 5: 制約更新と再帰
列と対角線の制約を更新して次行を探索
Step 6: 解の数を累積
各枝から返された解の数を合計

⚡ 計算量解析

時間計算量

O(N!)

バックトラッキングの本質的な複雑さ。ただし、ビット演算により定数倍が大幅に改善される。

空間計算量

O(N)

再帰スタックの深さがNに比例。ビットマスクにより追加のデータ構造が不要。

最適化効果

10-50x

従来の配列ベースの実装と比較して、10倍から50倍の速度向上が期待できる。

ビット演算による最適化のメリット

  • メモリ効率: 制約情報を整数1つで表現
  • 高速な制約チェック: ビット演算は非常に高速
  • 分岐削減: 利用不可能な位置を事前に除外
  • キャッシュ効率: データサイズが小さくCPUキャッシュを有効活用

🎓 学習ポイント

バックトラッキング + ビット演算: 古典的なアルゴリズムに現代的な最適化を適用
制約の効率的表現: 複雑な制約をシンプルなビットマスクで表現
2の補数の活用: 最下位ビット抽出に2の補数を巧妙に利用
メモリとCPUの最適化: 理論的複雑さは同じでも実行時間を大幅短縮