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 行時,根據之前行上的皇后位置,判斷能放在哪些列
- 把所有能放的列都嘗試一邊,每次嘗試修改路徑陣列表示當前的決策
- 用 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;
}
};
- 遍歷之前的皇后,檢查和當前位置是否衝突
#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;
}
};
- 遞迴帶返回值
#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;
}
};