N-Queens
Imagine you are organizing a chess tournament and need to place n queens on an n×n chessboard so that no two queens threaten each other. How can you arrange them safely?
Given an integer n, return all distinct solutions to the n-queens puzzle. Each solution contains a distinct board configuration of the n-queens' placement, where 'Q' and '.' both indicate a queen and an empty space respectively. No two queens can attack each other horizontally, vertically, or diagonally.
1 ≤ n ≤ 10Output all distinct solutions in any order"4"[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]There are two distinct ways to place 4 queens so that none attack each other.
- n = 1 → [['Q']]
- n = 2 → [] (no solutions)
- n = 3 → [] (no solutions)
- n = 8 → multiple solutions (classic chessboard size)
Invalid solutions with attacking queens are included
✅ Check both diagonals using row-col and row+col indices
Subsequent recursive calls see corrupted board state
✅ Reset board cell to '.' after recursive call returns
Solutions accumulate incorrectly or cause errors
✅ Initialize all data structures inside the main function or reset before each run
Missed or extra attacks cause wrong pruning and wrong answers
✅ Use correct left shift for one diagonal and right shift for the other
Code may crash or return incorrect results
✅ Add base case checks for n=1 or invalid inputs
Intuition
Place queens row by row, and for each row try every column. After placing a queen, check if the board is still valid by scanning all previously placed queens.
Algorithm
- Start from the first row.
- Try placing a queen in each column of the current row.
- After placing, check if the board is valid (no two queens attack).
- If valid, recurse to the next row; if all rows are placed, record the solution.
def solveNQueens(n):
def is_valid(board, row, col):
for i in range(row):
for j in range(n):
if board[i][j] == 'Q':
if j == col or abs(row - i) == abs(col - j):
return False
return True
def backtrack(row, board, solutions):
if row == n:
solutions.append([''.join(r) for r in board])
return
for col in range(n):
board[row][col] = 'Q'
if is_valid(board, row, col):
backtrack(row + 1, board, solutions)
board[row][col] = '.'
board = [['.' for _ in range(n)] for _ in range(n)]
solutions = []
backtrack(0, board, solutions)
return solutions
# Example usage:
if __name__ == '__main__':
print(solveNQueens(4))def is_valid(board, row, col):Defines a helper to check if placing a queen at (row, col) is safefor i in range(row):Check all previously placed queens in rows above current rowif j == col or abs(row - i) == abs(col - j):Check same column or diagonal conflictsif row == n:Base case: all rows have queens placed, record solutionimport java.util.*;
public class NQueensBrute {
public List<List<String>> solveNQueens(int n) {
List<List<String>> solutions = new ArrayList<>();
char[][] board = new char[n][n];
for (int i = 0; i < n; i++) Arrays.fill(board[i], '.');
backtrack(0, board, solutions, n);
return solutions;
}
private void backtrack(int row, char[][] board, List<List<String>> solutions, int n) {
if (row == n) {
List<String> solution = new ArrayList<>();
for (char[] r : board) solution.add(new String(r));
solutions.add(solution);
return;
}
for (int col = 0; col < n; col++) {
board[row][col] = 'Q';
if (isValid(board, row, col, n)) {
backtrack(row + 1, board, solutions, n);
}
board[row][col] = '.';
}
}
private boolean isValid(char[][] board, int row, int col, int n) {
for (int i = 0; i < row; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == 'Q') {
if (j == col || Math.abs(row - i) == Math.abs(col - j)) return false;
}
}
}
return true;
}
public static void main(String[] args) {
NQueensBrute solver = new NQueensBrute();
System.out.println(solver.solveNQueens(4));
}
}public List<List<String>> solveNQueens(int n)Entry point to solve the problem and store solutionsfor (int col = 0; col < n; col++)Try placing queen in each column of current rowif (isValid(board, row, col, n))Check if current placement is safe before recursingif (row == n)All queens placed, add current board to solutions#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> solutions;
vector<string> board(n, string(n, '.'));
backtrack(0, board, solutions, n);
return solutions;
}
private:
void backtrack(int row, vector<string>& board, vector<vector<string>>& solutions, int n) {
if (row == n) {
solutions.push_back(board);
return;
}
for (int col = 0; col < n; col++) {
board[row][col] = 'Q';
if (isValid(board, row, col, n)) {
backtrack(row + 1, board, solutions, n);
}
board[row][col] = '.';
}
}
bool isValid(const vector<string>& board, int row, int col, int n) {
for (int i = 0; i < row; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == 'Q') {
if (j == col || abs(row - i) == abs(col - j)) return false;
}
}
}
return true;
}
};
int main() {
Solution sol;
auto res = sol.solveNQueens(4);
for (auto& board : res) {
for (auto& row : board) cout << row << endl;
cout << endl;
}
return 0;
}vector<string> board(n, string(n, '.'))Initialize board with '.' indicating empty cellsfor (int col = 0; col < n; col++)Try placing queen in each column of current rowif (isValid(board, row, col, n))Validate placement before deeper recursionif (row == n)All queens placed, add current board to solutionsfunction solveNQueens(n) {
const solutions = [];
const board = Array.from({ length: n }, () => '.'.repeat(n).split(''));
function isValid(row, col) {
for (let i = 0; i < row; i++) {
for (let j = 0; j < n; j++) {
if (board[i][j] === 'Q') {
if (j === col || Math.abs(row - i) === Math.abs(col - j)) return false;
}
}
}
return true;
}
function backtrack(row) {
if (row === n) {
solutions.push(board.map(r => r.join('')));
return;
}
for (let col = 0; col < n; col++) {
board[row][col] = 'Q';
if (isValid(row, col)) {
backtrack(row + 1);
}
board[row][col] = '.';
}
}
backtrack(0);
return solutions;
}
// Example usage:
console.log(solveNQueens(4));const board = Array.from({ length: n }, () => '.'.repeat(n).split(''))Initialize 2D board with '.' for empty cellsfor (let col = 0; col < n; col++)Try placing queen in each column of current rowif (isValid(row, col))Check if current placement is safe before recursingif (row === n)All queens placed, add current board to solutionsO(n! * n)O(n^2)We try placing queens row by row, each row has up to n columns. Checking validity takes O(n) per placement, leading to factorial time in worst case.
This approach is too slow for big boards but is essential to grasp the problem and backtracking basics.
Intuition
Instead of scanning the entire board to check conflicts, keep sets or arrays to mark attacked columns and diagonals, so checking is O(1).
Algorithm
- Initialize sets to track attacked columns, and two diagonals (row-col and row+col).
- Place queens row by row, skipping columns that are attacked.
- When placing a queen, mark the column and diagonals as attacked.
- Backtrack by unmarking after recursive calls; record solutions when all rows are placed.
def solveNQueens(n):
solutions = []
cols = set()
diag1 = set() # row - col
diag2 = set() # row + col
board = [['.' for _ in range(n)] for _ in range(n)]
def backtrack(row):
if row == n:
solutions.append([''.join(r) for r in board])
return
for col in range(n):
if col in cols or (row - col) in diag1 or (row + col) in diag2:
continue
board[row][col] = 'Q'
cols.add(col)
diag1.add(row - col)
diag2.add(row + col)
backtrack(row + 1)
board[row][col] = '.'
cols.remove(col)
diag1.remove(row - col)
diag2.remove(row + col)
backtrack(0)
return solutions
# Example usage:
if __name__ == '__main__':
print(solveNQueens(4))cols = set()Tracks columns already occupied by queensdiag1 = set() # row - colTracks one diagonal direction to detect attacksif col in cols or (row - col) in diag1 or (row + col) in diag2:Skip positions attacked by any queencols.add(col)Mark column as attacked when placing a queenimport java.util.*;
public class NQueensPruned {
public List<List<String>> solveNQueens(int n) {
List<List<String>> solutions = new ArrayList<>();
Set<Integer> cols = new HashSet<>();
Set<Integer> diag1 = new HashSet<>();
Set<Integer> diag2 = new HashSet<>();
char[][] board = new char[n][n];
for (int i = 0; i < n; i++) Arrays.fill(board[i], '.');
backtrack(0, n, board, cols, diag1, diag2, solutions);
return solutions;
}
private void backtrack(int row, int n, char[][] board, Set<Integer> cols, Set<Integer> diag1, Set<Integer> diag2, List<List<String>> solutions) {
if (row == n) {
List<String> solution = new ArrayList<>();
for (char[] r : board) solution.add(new String(r));
solutions.add(solution);
return;
}
for (int col = 0; col < n; col++) {
if (cols.contains(col) || diag1.contains(row - col) || diag2.contains(row + col)) continue;
board[row][col] = 'Q';
cols.add(col);
diag1.add(row - col);
diag2.add(row + col);
backtrack(row + 1, n, board, cols, diag1, diag2, solutions);
board[row][col] = '.';
cols.remove(col);
diag1.remove(row - col);
diag2.remove(row + col);
}
}
public static void main(String[] args) {
NQueensPruned solver = new NQueensPruned();
System.out.println(solver.solveNQueens(4));
}
}Set<Integer> cols = new HashSet<>()Tracks columns with queens for O(1) conflict checksif (cols.contains(col) || diag1.contains(row - col) || diag2.contains(row + col)) continue;Skip attacked positions quicklycols.add(col);Mark column as attacked when placing queencols.remove(col);Unmark column when backtracking#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
using namespace std;
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> solutions;
vector<string> board(n, string(n, '.'));
unordered_set<int> cols, diag1, diag2;
backtrack(0, n, board, cols, diag1, diag2, solutions);
return solutions;
}
private:
void backtrack(int row, int n, vector<string>& board, unordered_set<int>& cols, unordered_set<int>& diag1, unordered_set<int>& diag2, vector<vector<string>>& solutions) {
if (row == n) {
solutions.push_back(board);
return;
}
for (int col = 0; col < n; col++) {
if (cols.count(col) || diag1.count(row - col) || diag2.count(row + col)) continue;
board[row][col] = 'Q';
cols.insert(col);
diag1.insert(row - col);
diag2.insert(row + col);
backtrack(row + 1, n, board, cols, diag1, diag2, solutions);
board[row][col] = '.';
cols.erase(col);
diag1.erase(row - col);
diag2.erase(row + col);
}
}
};
int main() {
Solution sol;
auto res = sol.solveNQueens(4);
for (auto& board : res) {
for (auto& row : board) cout << row << endl;
cout << endl;
}
return 0;
}unordered_set<int> cols, diag1, diag2;Use hash sets for constant time conflict checksif (cols.count(col) || diag1.count(row - col) || diag2.count(row + col)) continue;Skip invalid positions immediatelycols.insert(col);Mark column attacked when placing queencols.erase(col);Unmark column when backtrackingfunction solveNQueens(n) {
const solutions = [];
const cols = new Set();
const diag1 = new Set(); // row - col
const diag2 = new Set(); // row + col
const board = Array.from({ length: n }, () => '.'.repeat(n).split(''));
function backtrack(row) {
if (row === n) {
solutions.push(board.map(r => r.join('')));
return;
}
for (let col = 0; col < n; col++) {
if (cols.has(col) || diag1.has(row - col) || diag2.has(row + col)) continue;
board[row][col] = 'Q';
cols.add(col);
diag1.add(row - col);
diag2.add(row + col);
backtrack(row + 1);
board[row][col] = '.';
cols.delete(col);
diag1.delete(row - col);
diag2.delete(row + col);
}
}
backtrack(0);
return solutions;
}
console.log(solveNQueens(4));const cols = new Set();Track attacked columns efficientlyif (cols.has(col) || diag1.has(row - col) || diag2.has(row + col)) continue;Skip invalid queen placements quicklycols.add(col);Mark column attacked when placing queencols.delete(col);Remove column attack mark when backtrackingO(n!)O(n)Pruning reduces the number of invalid placements checked, so time is roughly factorial but faster than brute force. Space is O(n) for sets and recursion stack.
This is the standard approach to code in interviews for N-Queens due to its efficiency and clarity.
Intuition
Represent columns and diagonals as bits in integers. Use bitwise operations to quickly find free positions and update attacks.
Algorithm
- Use three integers to track attacked columns, and two diagonals.
- At each row, compute available positions by bitwise negation and AND.
- Iterate over available positions by extracting the rightmost set bit.
- Place queen, update bitmasks with bitwise OR and shifts, recurse.
- Backtrack by restoring bitmasks; record solutions when done.
def solveNQueens(n):
solutions = []
board = [['.' for _ in range(n)] for _ in range(n)]
def backtrack(row, cols, diag1, diag2):
if row == n:
solutions.append([''.join(r) for r in board])
return
available_positions = ((1 << n) - 1) & (~(cols | diag1 | diag2))
while available_positions:
position = available_positions & (-available_positions) # rightmost 1-bit
available_positions &= available_positions - 1 # remove rightmost 1-bit
col = (position & -position).bit_length() - 1
board[row][col] = 'Q'
backtrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1)
board[row][col] = '.'
backtrack(0, 0, 0, 0)
return solutions
# Example usage:
if __name__ == '__main__':
print(solveNQueens(4))available_positions = ((1 << n) - 1) & (~(cols | diag1 | diag2))Calculate free columns by masking attacked positionsposition = available_positions & (-available_positions)Extract rightmost free position to trycol = (position & -position).bit_length() - 1Convert bitmask position to column indexbacktrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1)Update attacked columns and diagonals for next rowimport java.util.*;
public class NQueensBitmask {
private List<List<String>> solutions = new ArrayList<>();
private int size;
private char[][] board;
public List<List<String>> solveNQueens(int n) {
size = n;
board = new char[n][n];
for (int i = 0; i < n; i++) Arrays.fill(board[i], '.');
backtrack(0, 0, 0, 0);
return solutions;
}
private void backtrack(int row, int cols, int diag1, int diag2) {
if (row == size) {
List<String> solution = new ArrayList<>();
for (char[] r : board) solution.add(new String(r));
solutions.add(solution);
return;
}
int availablePositions = ((1 << size) - 1) & ~(cols | diag1 | diag2);
while (availablePositions != 0) {
int position = availablePositions & (-availablePositions);
availablePositions &= availablePositions - 1;
int col = Integer.numberOfTrailingZeros(position);
board[row][col] = 'Q';
backtrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1);
board[row][col] = '.';
}
}
public static void main(String[] args) {
NQueensBitmask solver = new NQueensBitmask();
System.out.println(solver.solveNQueens(4));
}
}int availablePositions = ((1 << size) - 1) & ~(cols | diag1 | diag2);Calculate free columns using bitmaskingint position = availablePositions & (-availablePositions);Extract rightmost free bit to tryint col = Integer.numberOfTrailingZeros(position);Convert bitmask to column indexbacktrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1);Update attacked columns and diagonals for next row#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
size = n;
board = vector<string>(n, string(n, '.'));
backtrack(0, 0, 0, 0);
return solutions;
}
private:
int size;
vector<string> board;
vector<vector<string>> solutions;
void backtrack(int row, int cols, int diag1, int diag2) {
if (row == size) {
solutions.push_back(board);
return;
}
int availablePositions = ((1 << size) - 1) & (~(cols | diag1 | diag2));
while (availablePositions) {
int position = availablePositions & (-availablePositions);
availablePositions &= availablePositions - 1;
int col = __builtin_ctz(position);
board[row][col] = 'Q';
backtrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1);
board[row][col] = '.';
}
}
};
int main() {
Solution sol;
auto res = sol.solveNQueens(4);
for (auto& board : res) {
for (auto& row : board) cout << row << endl;
cout << endl;
}
return 0;
}int availablePositions = ((1 << size) - 1) & (~(cols | diag1 | diag2));Calculate free columns using bitmaskingint position = availablePositions & (-availablePositions);Extract rightmost free bit to tryint col = __builtin_ctz(position);Count trailing zeros to get column indexbacktrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1);Update attacked columns and diagonals for next rowfunction solveNQueens(n) {
const solutions = [];
const board = Array.from({ length: n }, () => '.'.repeat(n).split(''));
function backtrack(row, cols, diag1, diag2) {
if (row === n) {
solutions.push(board.map(r => r.join('')));
return;
}
let availablePositions = ((1 << n) - 1) & ~(cols | diag1 | diag2);
while (availablePositions) {
let position = availablePositions & -availablePositions;
availablePositions &= availablePositions - 1;
let col = Math.log2(position);
board[row][col] = 'Q';
backtrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1);
board[row][col] = '.';
}
}
backtrack(0, 0, 0, 0);
return solutions;
}
console.log(solveNQueens(4));let availablePositions = ((1 << n) - 1) & ~(cols | diag1 | diag2);Calculate free columns using bitmaskinglet position = availablePositions & -availablePositions;Extract rightmost free bit to trylet col = Math.log2(position);Convert bitmask to column index using log2backtrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1);Update attacked columns and diagonals for next rowO(n!)O(n)Bitmasking reduces overhead and speeds up pruning, but the search space remains factorial. Space is minimal due to integer bitmasks.
Bitmasking is an advanced optimization often appreciated in interviews for demonstrating mastery of bitwise operations.
| Approach | Time | Space | Stack Risk | Reconstruct | Use In Interview |
|---|---|---|---|---|---|
| 1. Brute Force | O(n! * n) | O(n^2) | Yes (deep recursion) | Yes | Mention only - never code |
| 2. Backtracking with Column and Diagonal Pruning | O(n!) | O(n) | Yes (deep recursion) | Yes | Code this approach in interviews |
| 3. Bitmask Optimization | O(n!) | O(n) | Yes (deep recursion) | Yes | Mention or code if comfortable with bitwise ops |
How to Present
Step 1: Clarify the problem and constraints with the interviewer.Step 2: Explain the brute force approach to show understanding of the problem.Step 3: Introduce pruning with sets to optimize validation.Step 4: If comfortable, discuss bitmask optimization for further speed.Step 5: Code the pruning approach as it balances clarity and efficiency.Step 6: Test with edge cases and explain complexity.
Time Allocation
What the Interviewer Tests
Ability to implement backtracking correctly, optimize pruning, use data structures effectively, and reason about complexity.
Common Follow-ups
- How to count the total number of solutions without storing them? → Use a counter instead of storing boards.
- Can you solve for n > 15 efficiently? → Bitmasking is required for performance.
- How to modify for N-Rooks or other pieces? → Adjust attack rules accordingly.
- Can you output one valid solution instead of all? → Stop recursion after first solution.
When to Use
1) Need to place items on a grid with constraints; 2) Constraints involve rows, columns, diagonals; 3) Search space is large; 4) Backtracking with pruning is needed.
