N皇后问题

N皇后问题

  • 时间复杂度为 O(n!)

51. N 皇后

经典做法

#include <string>
#include <iostream>
#include <vector>
#include <unordered_set>

using namespace std;

class Solution {
public:
    vector<vector<string>> res;
    // 分别标记列和两个方向的斜线上是否已经存在皇后
    unordered_set<int> columns;
    unordered_set<int> diagonals1;
    unordered_set<int> diagonals2;

    vector<vector<string>> solveNQueens(int n) {
        // 记录每一行上,皇后所在的列
        vector<int> queens(n, -1);
        backtrack(queens, n, 0);
        return res;
    }

    void backtrack(vector<int> &queens, int n, int row) {
        if (row == n) {
            // 结算这种情况
            vector<string> board = generateBoard(queens, n);
            res.push_back(board);
            return;
        }
        // 尝试在每一列上放
        for (int i = 0; i < n; i++) {
            // 当前列已有皇后
            if (columns.find(i) != columns.end()) continue;
            // 主斜线已有
            int d1 = row - i;
            if (diagonals1.find(d1) != diagonals1.end()) continue;
            // 副斜线已有
            int d2 = row + i;
            if (diagonals2.find(d2) != diagonals2.end()) continue;

            // 把 row 行的皇后放在 i 列
            queens[row] = i;
            // 标记列和两个方向的斜线上已经存在皇后
            columns.insert(i);
            diagonals1.insert(d1);
            diagonals2.insert(d2);
            // 递归处理下一行
            backtrack(queens, n, row + 1);
            // 取消标记
            queens[row] = -1;
            columns.erase(i);
            diagonals1.erase(d1);
            diagonals2.erase(d2);
        }
    }

    vector<string> generateBoard(vector<int> &queens, int n) {
        vector<string> board;
        for (int i = 0; i < n; i++) {
            string row = string(n, '.');
            row[queens[i]] = 'Q';
            board.push_back(row);
        }
        return board;
    }
};

位运算

#include <string>
#include <vector>

using namespace std;

class Solution {
public:
    vector<vector<string>> res;
    // 记录皇后放的位置,queens[i] 二进制位为 1 的地方才是放皇后的位置
    // 也可以直接记录具体列号,这样生成结果时快些
    vector<int> queens;
    int limit;

    vector<vector<string>> solveNQueens(int n) {
        // 把低 n 位变成 1
        limit = (1 << n) - 1;
        // -1 的位置表示没有皇后
        queens.resize(n, -1);
        backtrack(n, 0, 0, 0, 0);
        return res;
    }

    void backtrack(int n, int row, int columns, int diagonals1, int diagonals2) {
        if (columns == limit) {
            // 生成结果
            res.emplace_back(generateBoard(n));
            return;
        }
        // 0 的位置能放,1 的位置不能放
        int ban = columns | diagonals1 | diagonals2;
        // candidate 为 1 的地方都是可以放皇后的
        int candidate = limit & (~ban);
        // 尝试每个位置
        while (candidate != 0) {
            // 最右侧的 1
            int place = candidate & (-candidate);
            queens[row] = place;
            // 累计上当前皇后的影响,(diagonals1 | place) >> 1 的意思是当前 place 位置放皇后的情况下,主斜线对下一行的影响
            backtrack(n, row + 1, columns | place, (diagonals1 | place) >> 1, (diagonals2 | place) << 1);
            // 删掉最右侧的 1
            candidate ^= place;
        }
    }

    vector<string> generateBoard(int n) {
        vector<string> board;
        for (int i = 0; i < n; i++) {
            string str;
            for (int j = 0; j < n; ++j) {
                if ((queens[i] & (1 << j)) != 0) {
                    str += 'Q';
                } else {
                    str += '.';
                }
            }
            board.emplace_back(str);
        }
        return board;
    }
};

52. N 皇后 II

经典做法

  • 常数时间慢

  • 过程

    • 用数组记录每一行的皇后所在列
    • 到 row 行时,根据之前行上的皇后位置,判断能放在哪些列
    • 把所有能放的列都尝试一边,每次尝试修改路径数组表示当前的决策
  1. 用 set 标记列和斜线上是否已经存在皇后
#include <string>
#include <iostream>
#include <vector>
#include <unordered_set>

using namespace std;

class Solution {
public:
    // 分别标记列和两个方向的斜线上是否已经存在皇后
    unordered_set<int> columns;
    unordered_set<int> diagonals1;
    unordered_set<int> diagonals2;
    // 记录每行的皇后在哪一列
    vector<int> queens;
    int res;

    void backtrack(int n, int row) {
        if (row == n) {
            res++;
            return;
        }
        for (int i = 0; i < n; ++i) {
            // 如果不能放就跳过
            if (columns.find(i) != columns.end()) continue;
            int d1 = i - row;
            if (diagonals1.find(d1) != diagonals1.end()) continue;
            int d2 = i + row;
            if (diagonals2.find(d2) != diagonals2.end()) continue;

            queens[row] = i;
            // 标记
            columns.emplace(i);
            diagonals1.emplace(d1);
            diagonals2.emplace(d2);
            // 递归处理子问题
            backtrack(n, row + 1);
            // 回溯
            queens[row] = -1;
            columns.erase(i);
            diagonals1.erase(d1);
            diagonals2.erase(d2);
        }

    }

    int totalNQueens(int n) {
        res = 0;
        // -1 表示没有放皇后
        queens.resize(n, -1);
        backtrack(n, 0);
        return res;
    }
};
  1. 遍历之前的皇后,检查和当前位置是否冲突
#include <string>
#include <iostream>
#include <vector>
#include <unordered_set>

using namespace std;

class Solution {
public:
    // 记录每行的皇后在哪一列
    vector<int> queens;
    int res;

    void backtrack(int n, int row) {
        if (row == n) {
            res++;
            return;
        }
        for (int column = 0; column < n; ++column) {
            if (!isValid(row, column)) continue;
            queens[row] = column;
            // 递归处理子问题
            backtrack(n, row + 1);
            // 下个尝试的列会覆盖掉 queens[row],所以不需要手动回溯
        }
    }

    // 判断能否在当前位置放皇后
    bool isValid(int row, int column) {
        for (int i = 0; i < row; ++i)
            if (column == queens[i] || abs(row - i) == abs(column - queens[i]))
                return false;
        return true;
    }

    int totalNQueens(int n) {
        res = 0;
        // -1 表示没有放皇后
        queens.resize(n, -1);
        backtrack(n, 0);
        return res;
    }
};
  1. 递归带返回值
#include <string>
#include <iostream>
#include <vector>
#include <unordered_set>

using namespace std;

class Solution {
public:
    // 记录每行的皇后在哪一列
    vector<int> queens;

    // 返回: 0...row-1 行已经摆完了,row....n - 1 行可以去尝试的情况下还能找到几种有效的方法
    int backtrack(int n, int row) {
        if (row == n) return 1;
        int res = 0;
        for (int column = 0; column < n; ++column) {
            if (!isValid(row, column)) continue;
            queens[row] = column;
            // 递归处理子问题
            res += backtrack(n, row + 1);
        }
        return res;
    }

    // 判断能否在当前位置放皇后
    bool isValid(int row, int column) {
        for (int i = 0; i < row; ++i)
            if (column == queens[i] || abs(row - i) == abs(column - queens[i]))
                return false;
        return true;
    }

    int totalNQueens(int n) {
        queens.resize(n, -1);
        return backtrack(n, 0);
    }
};

位运算

  • 常数时间快
using namespace std;

class Solution {
public:
    int res;
    int limit;

    // columns 按位标记哪些列上已经有皇后了
    // diagonals1、diagonals2 标记两种方向的斜线上对当前行的影响,为 1 的位置表示在这个斜线上已经有过皇后了
    void backtrack(int columns, int diagonals1, int diagonals2) {
        if (columns == limit) {
            // 低 n 位全是 1,说明每一列都放皇后了,结束
            res++;
            return;
        }

        // 0 的位置能放,1 的位置不能放
        int ban = columns | diagonals1 | diagonals2;
        // candidate 为 1 的地方都是可以放皇后的
        int candidate = limit & (~ban);
        // 尝试每个位置
        while (candidate != 0) {
            // 放皇后的具体位置:取出 candidate 最右侧的 1,也就是列从左往右数第一个能放皇后的列
            int place = candidate & (-candidate);
            // 累计上当前皇后的影响,(diagonals1 | place) >> 1 的意思是当前 place 位置放皇后的情况下,主斜线对下一行的影响
            backtrack(columns | place, (diagonals1 | place) >> 1, (diagonals2 | place) << 1);
            // 把 candidate 最右侧的 1 变成 0,然后尝试下个能放皇后的位置
            candidate ^= place;
        }
    }

    int totalNQueens(int n) {
        res = 0;
        // 把低 n 位变成 1
        limit = (1 << n) - 1;
        backtrack(0, 0, 0);
        return res;
    }
};
posted @ 2024-10-06 21:07  n1ce2cv  阅读(4)  评论(0编辑  收藏  举报