Backtracking
Build answers one choice at a time: choose, explore, un-choose, and prune any branch that can't lead to a solution.
How It Works
Backtracking solves problems where an answer is built from a sequence of choices: which element to add next, which number goes in the next position, where the next queen sits. You make a choice, recurse to make the rest, then undo the choice and try the next option. It's brute force over a tree of choices, done with one shared path that you edit in place instead of copying state at every step.
Every backtracking function runs the same three moves inside a loop over the options:
- Choose: add an option to the current path, and mark it used if that matters.
- Explore: recurse to make the remaining choices.
- Un-choose: remove the option again. The path is now exactly what it was before, so the next option starts from a clean state.
[ ]
|
+---------+---------+
| | |
[1] [2] [3]
| |
+---+---+ |
| | |
[1,2] [1,3] [2,3]
|
[1,2,3]Subsets, Step by Step
Step 1 of 16
decision tree
path
result
- Current node, newest save
- Rest of the path
- Finished
Start at the root with an empty path. Every node of the tree is a subset, so backtrack(0) saves [] before it tries a single option.
All 16 steps as text
- Start at the root with an empty
path. Every node of the tree is a subset, sobacktrack(0)saves[]before it tries a single option. - Choose
1: push it ontopathand save a copy,[1]. Only numbers to the right of1are offered next:2, 3. - Choose
2: push it ontopathand save a copy,[1, 2]. Only numbers to the right of2are offered next:3. - Choose
3: push it ontopathand save a copy,[1, 2, 3]. Nothing lies to the right of3, so the loop has no options and this node is a leaf. - Un-choose
3: pop it, sopathis[1, 2]again. That was the last option, so[1, 2]has no branches left to try. - Un-choose
2: pop it, sopathis[1]again. The loop moves on to3, starting from that same path. - Choose
3: push it ontopathand save a copy,[1, 3]. Nothing lies to the right of3, so the loop has no options and this node is a leaf. - Un-choose
3: pop it, sopathis[1]again. That was the last option, so[1]has no branches left to try. - Un-choose
1: pop it, sopathis empty again. The loop moves on to2, starting from that same path. - Choose
2: push it ontopathand save a copy,[2]. Only3can follow it.[2, 1]is never built, because it would repeat[1, 2]. - Choose
3: push it ontopathand save a copy,[2, 3]. Nothing lies to the right of3, so the loop has no options and this node is a leaf. - Un-choose
3: pop it, sopathis[2]again. That was the last option, so[2]has no branches left to try. - Un-choose
2: pop it, sopathis empty again. The loop moves on to3, starting from that same path. - Choose
3: push it ontopathand save a copy,[3]. Nothing lies to the right of3, so the loop has no options and this node is a leaf. - Un-choose
3: pop it, sopathis empty again. That was the last option, so the root has no branches left to try. backtrack(0)returns and the search is over.resultholds all8subsets, one per node, because each number is either in or out:2^3.
The running time is the size of that tree, which is exponential, so the way to make backtracking fast enough is pruning: test a partial answer as early as you can and return before exploring a subtree that can't contain a solution. Combination Sum stops as soon as the running total passes the target. N-Queens never places a queen on a square that's already attacked.
- Subsets and combinations. Pass a
startindex and only offer options from there on, so each group is built in exactly one order. Every node is an answer (subsets), or every node that hits a target is (combinations). - Permutations. Order matters, so every unused option is open at every step. A
usedarray tracks what's on the path, and only full-length leaves are answers. - Constraint placement. N-Queens, Sudoku, Word Search. Check that a placement is legal before making it, and stop at the first solution if one is all you need.
When to Reach for It
- The question asks for all subsets, combinations, permutations, partitions or board arrangements, not just how many there are.
- The input is tiny (n up to about 10 or 20), which hints that an exponential search is expected.
- You're placing things under rules that interact: queens that can't attack, Sudoku digits, a word traced through a grid.
- A choice made early limits the choices later, and you may need to abandon a partial answer halfway through.
Complexity
| Operation | Time | Space |
|---|---|---|
| Subsets of n items | O(n · 2^n) | O(n) beyond the output |
| Permutations of n items | O(n · n!) | O(n) beyond the output |
| Combination Sum (target t, smallest candidate m) | O(n^(t/m + 1)) | O(n + t/m) beyond the output |
| N-Queens on an n x n board | O(n!) | O(n) beyond the output |
The Template
Subsets
Return every subset of an array of distinct numbers (LeetCode's Subsets). Every node of the decision tree is a subset, so backtrack saves the path as soon as it arrives, before branching. The loop then offers each number from start onward.
- Choose: add
nums[i]to the end ofpath. - Explore:
backtrack(i + 1)builds every subset that begins with the current path. Passingi + 1means a number is only ever followed by numbers to its right, so[1, 2]gets built and[2, 1]never does. - Un-choose: remove
nums[i]from the end ofpathagain. Without it, numbers from finished branches pile up on the path and leak into every answer after them.
There are 2^n subsets, because each number is either in or out, and copying each one costs up to O(n). That gives O(n · 2^n) time. Beyond the output, the path and the call stack are both at most n deep.
Time O(n · 2^n)Space O(n) beyond the output
def subsets(nums: list[int]) -> list[list[int]]:
"""Every subset of `nums`, built by choosing, exploring, then un-choosing."""
result: list[list[int]] = []
path: list[int] = []
def backtrack(start: int) -> None:
result.append(path[:]) # Every node in the tree is a subset. Save a copy.
for i in range(start, len(nums)):
path.append(nums[i]) # Choose nums[i].
backtrack(i + 1) # Explore: only numbers to its right, so no subset repeats.
path.pop() # Un-choose, so the next i starts from the same path.
backtrack(0)
return result
if __name__ == "__main__":
print(subsets([1, 2, 3])) # [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]
print(subsets([0])) # [[], [0]]
print(subsets([])) # [[]]// Every subset of `nums`, built by choosing, exploring, then un-choosing.
export function subsets(nums: number[]): number[][] {
const result: number[][] = [];
const path: number[] = [];
function backtrack(start: number): void {
result.push([...path]); // Every node in the tree is a subset. Save a copy.
for (let i = start; i < nums.length; i++) {
path.push(nums[i]); // Choose nums[i].
backtrack(i + 1); // Explore: only numbers to its right, so no subset repeats.
path.pop(); // Un-choose, so the next i starts from the same path.
}
}
backtrack(0);
return result;
}
console.log(JSON.stringify(subsets([1, 2, 3]))); // [[],[1],[1,2],[1,2,3],[1,3],[2],[2,3],[3]]
console.log(JSON.stringify(subsets([0]))); // [[],[0]]
console.log(JSON.stringify(subsets([]))); // [[]]package main
import (
"fmt"
"slices"
)
// subsets returns every subset of nums, built by choosing, exploring, then
// un-choosing.
func subsets(nums []int) [][]int {
result := [][]int{}
path := []int{}
var backtrack func(start int)
backtrack = func(start int) {
result = append(result, slices.Clone(path)) // Every node in the tree is a subset. Save a copy.
for i := start; i < len(nums); i++ {
path = append(path, nums[i]) // Choose nums[i].
backtrack(i + 1) // Explore: only numbers to its right, so no subset repeats.
path = path[:len(path)-1] // Un-choose, so the next i starts from the same path.
}
}
backtrack(0)
return result
}
func main() {
fmt.Println(subsets([]int{1, 2, 3})) // [[] [1] [1 2] [1 2 3] [1 3] [2] [2 3] [3]]
fmt.Println(subsets([]int{0})) // [[] [0]]
fmt.Println(subsets([]int{})) // [[]]
}using System;
using System.Collections.Generic;
using System.Linq;
public class Solution
{
// Every subset of nums, built by choosing, exploring, then un-choosing.
public static List<List<int>> Subsets(int[] nums)
{
var result = new List<List<int>>();
var path = new List<int>();
void Backtrack(int start)
{
result.Add(new List<int>(path)); // Every node in the tree is a subset. Save a copy.
for (int i = start; i < nums.Length; i++)
{
path.Add(nums[i]); // Choose nums[i].
Backtrack(i + 1); // Explore: only numbers to its right, so no subset repeats.
path.RemoveAt(path.Count - 1); // Un-choose, so the next i starts from the same path.
}
}
Backtrack(0);
return result;
}
public static void Main()
{
Console.WriteLine(Format(Subsets(new[] { 1, 2, 3 }))); // [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]
Console.WriteLine(Format(Subsets(new[] { 0 }))); // [[], [0]]
Console.WriteLine(Format(Subsets(new int[] { }))); // [[]]
}
static string Format(List<List<int>> lists) =>
"[" + string.Join(", ", lists.Select(l => "[" + string.Join(", ", l) + "]")) + "]";
}import java.util.ArrayList;
import java.util.List;
public class Solution {
// Every subset of nums, built by choosing, exploring, then un-choosing.
static List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(nums, 0, new ArrayList<>(), result);
return result;
}
static void backtrack(int[] nums, int start, List<Integer> path, List<List<Integer>> result) {
result.add(new ArrayList<>(path)); // Every node in the tree is a subset. Save a copy.
for (int i = start; i < nums.length; i++) {
path.add(nums[i]); // Choose nums[i].
backtrack(nums, i + 1, path, result); // Explore: only numbers to its right, so no subset repeats.
path.remove(path.size() - 1); // Un-choose, so the next i starts from the same path.
}
}
public static void main(String[] args) {
System.out.println(subsets(new int[] {1, 2, 3})); // [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]
System.out.println(subsets(new int[] {0})); // [[], [0]]
System.out.println(subsets(new int[] {})); // [[]]
}
}Worked Problems
Each solution stays folded until you open it, so you can try the problem first. Every one is a complete program in Python, TypeScript, Go, C# and Java, run and checked against its expected output.
Problem
Given an array of distinct integers, return every possible ordering of them. The orderings can come back in any order.
Example: [1, 2, 3] returns all six orderings: [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]].
Show SolutionHide Solution
Approach
Order matters here, so the start index from Subsets can't be used: 2 has to be allowed before 1. Instead, every step looks at every number and skips the ones already on the path. A used array makes that check O(1).
Only leaves are answers. When the path holds all n numbers, save a copy. Choose marks used[i] and appends the number, and un-choose pops it and clears the mark, so the number is free again for a later position in a different ordering.
There are n! leaves, each copied in O(n), and fewer than 2 · n! internal nodes, each looping over all n numbers. Both come to O(n · n!).
Time O(n · n!)Space O(n) beyond the output
def permute(nums: list[int]) -> list[list[int]]:
"""Every ordering of the distinct numbers in `nums`."""
result: list[list[int]] = []
path: list[int] = []
used = [False] * len(nums)
def backtrack() -> None:
if len(path) == len(nums):
result.append(path[:]) # Every position is filled: a leaf. Save a copy.
return
for i in range(len(nums)):
if used[i]:
continue # Already placed earlier in this ordering.
used[i] = True # Choose nums[i] for the next position.
path.append(nums[i])
backtrack() # Explore: fill the remaining positions.
path.pop() # Un-choose, so nums[i] is free for a later position.
used[i] = False
backtrack()
return result
if __name__ == "__main__":
print(permute([1, 2, 3])) # [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
print(permute([0, 1])) # [[0, 1], [1, 0]]
print(permute([1])) # [[1]]// Every ordering of the distinct numbers in `nums`.
export function permute(nums: number[]): number[][] {
const result: number[][] = [];
const path: number[] = [];
const used: boolean[] = new Array<boolean>(nums.length).fill(false);
function backtrack(): void {
if (path.length === nums.length) {
result.push([...path]); // Every position is filled: a leaf. Save a copy.
return;
}
for (let i = 0; i < nums.length; i++) {
if (used[i]) continue; // Already placed earlier in this ordering.
used[i] = true; // Choose nums[i] for the next position.
path.push(nums[i]);
backtrack(); // Explore: fill the remaining positions.
path.pop(); // Un-choose, so nums[i] is free for a later position.
used[i] = false;
}
}
backtrack();
return result;
}
console.log(JSON.stringify(permute([1, 2, 3]))); // [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
console.log(JSON.stringify(permute([0, 1]))); // [[0,1],[1,0]]
console.log(JSON.stringify(permute([1]))); // [[1]]package main
import (
"fmt"
"slices"
)
// permute returns every ordering of the distinct numbers in nums.
func permute(nums []int) [][]int {
result := [][]int{}
path := []int{}
used := make([]bool, len(nums))
var backtrack func()
backtrack = func() {
if len(path) == len(nums) {
result = append(result, slices.Clone(path)) // Every position is filled: a leaf. Save a copy.
return
}
for i := range nums {
if used[i] {
continue // Already placed earlier in this ordering.
}
used[i] = true // Choose nums[i] for the next position.
path = append(path, nums[i])
backtrack() // Explore: fill the remaining positions.
path = path[:len(path)-1] // Un-choose, so nums[i] is free for a later position.
used[i] = false
}
}
backtrack()
return result
}
func main() {
fmt.Println(permute([]int{1, 2, 3})) // [[1 2 3] [1 3 2] [2 1 3] [2 3 1] [3 1 2] [3 2 1]]
fmt.Println(permute([]int{0, 1})) // [[0 1] [1 0]]
fmt.Println(permute([]int{1})) // [[1]]
}using System;
using System.Collections.Generic;
using System.Linq;
public class Solution
{
// Every ordering of the distinct numbers in nums.
public static List<List<int>> Permute(int[] nums)
{
var result = new List<List<int>>();
var path = new List<int>();
var used = new bool[nums.Length];
void Backtrack()
{
if (path.Count == nums.Length)
{
result.Add(new List<int>(path)); // Every position is filled: a leaf. Save a copy.
return;
}
for (int i = 0; i < nums.Length; i++)
{
if (used[i]) continue; // Already placed earlier in this ordering.
used[i] = true; // Choose nums[i] for the next position.
path.Add(nums[i]);
Backtrack(); // Explore: fill the remaining positions.
path.RemoveAt(path.Count - 1); // Un-choose, so nums[i] is free for a later position.
used[i] = false;
}
}
Backtrack();
return result;
}
public static void Main()
{
Console.WriteLine(Format(Permute(new[] { 1, 2, 3 }))); // [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
Console.WriteLine(Format(Permute(new[] { 0, 1 }))); // [[0, 1], [1, 0]]
Console.WriteLine(Format(Permute(new[] { 1 }))); // [[1]]
}
static string Format(List<List<int>> lists) =>
"[" + string.Join(", ", lists.Select(l => "[" + string.Join(", ", l) + "]")) + "]";
}import java.util.ArrayList;
import java.util.List;
public class Solution {
// Every ordering of the distinct numbers in nums.
static List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(nums, new boolean[nums.length], new ArrayList<>(), result);
return result;
}
static void backtrack(int[] nums, boolean[] used, List<Integer> path, List<List<Integer>> result) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path)); // Every position is filled: a leaf. Save a copy.
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) continue; // Already placed earlier in this ordering.
used[i] = true; // Choose nums[i] for the next position.
path.add(nums[i]);
backtrack(nums, used, path, result); // Explore: fill the remaining positions.
path.remove(path.size() - 1); // Un-choose, so nums[i] is free for a later position.
used[i] = false;
}
}
public static void main(String[] args) {
System.out.println(permute(new int[] {1, 2, 3})); // [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
System.out.println(permute(new int[] {0, 1})); // [[0, 1], [1, 0]]
System.out.println(permute(new int[] {1})); // [[1]]
}
}Problem
You're given distinct positive integers candidates and a positive target. Return every combination of candidates that adds up to target, where each candidate can be used as many times as you like. Two combinations count as the same if they use each number the same number of times, so list each one once.
Example: candidates = [2, 3, 6, 7] and target = 7 returns [[2, 2, 3], [7]].
Show SolutionHide Solution
Approach
This is the Subsets template with two changes. First, recurse with i instead of i + 1, because the number you just chose may be chosen again. The start index still matters: it means a combination is always built in non-decreasing order, so [2, 2, 3] is found once and [2, 3, 2] and [3, 2, 2] never are.
Second, track remaining, the amount still needed. When it reaches exactly 0, the path is an answer. Sorting the candidates first gives you a cheap prune: the first candidate bigger than remaining ends the loop, because every candidate after it is bigger still.
Call the target t and the smallest candidate m. Each step subtracts at least m, so a path is at most t/m numbers long, and each node branches at most n ways. That bounds the tree at O(n^(t/m + 1)) nodes. It's a loose bound, because the start index and the break cut most of those branches in practice.
Time O(n^(t/m + 1))Space O(n + t/m) beyond the output
def combination_sum(candidates: list[int], target: int) -> list[list[int]]:
"""Every unique combination of `candidates` that sums to `target`, where each
candidate can be used any number of times. Each combination is ascending."""
candidates = sorted(candidates)
result: list[list[int]] = []
path: list[int] = []
def backtrack(start: int, remaining: int) -> None:
if remaining == 0:
result.append(path[:]) # Hit the target exactly. Save a copy.
return
for i in range(start, len(candidates)):
if candidates[i] > remaining:
break # Sorted, so every later candidate is too big too.
path.append(candidates[i]) # Choose candidates[i].
backtrack(i, remaining - candidates[i]) # Explore from i, not i + 1: reuse is allowed.
path.pop() # Un-choose.
backtrack(0, target)
return result
if __name__ == "__main__":
print(combination_sum([2, 3, 6, 7], 7)) # [[2, 2, 3], [7]]
print(combination_sum([2, 3, 5], 8)) # [[2, 2, 2, 2], [2, 3, 3], [3, 5]]
print(combination_sum([2], 1)) # []// Every unique combination of `candidates` that sums to `target`, where each
// candidate can be used any number of times. Each combination is ascending.
export function combinationSum(candidates: number[], target: number): number[][] {
const sorted = [...candidates].sort((a, b) => a - b);
const result: number[][] = [];
const path: number[] = [];
function backtrack(start: number, remaining: number): void {
if (remaining === 0) {
result.push([...path]); // Hit the target exactly. Save a copy.
return;
}
for (let i = start; i < sorted.length; i++) {
if (sorted[i] > remaining) break; // Sorted, so every later candidate is too big too.
path.push(sorted[i]); // Choose sorted[i].
backtrack(i, remaining - sorted[i]); // Explore from i, not i + 1: reuse is allowed.
path.pop(); // Un-choose.
}
}
backtrack(0, target);
return result;
}
console.log(JSON.stringify(combinationSum([2, 3, 6, 7], 7))); // [[2,2,3],[7]]
console.log(JSON.stringify(combinationSum([2, 3, 5], 8))); // [[2,2,2,2],[2,3,3],[3,5]]
console.log(JSON.stringify(combinationSum([2], 1))); // []package main
import (
"fmt"
"slices"
)
// combinationSum returns every unique combination of candidates that sums to
// target, where each candidate can be used any number of times. Each
// combination is ascending.
func combinationSum(candidates []int, target int) [][]int {
sorted := slices.Clone(candidates)
slices.Sort(sorted)
result := [][]int{}
path := []int{}
var backtrack func(start, remaining int)
backtrack = func(start, remaining int) {
if remaining == 0 {
result = append(result, slices.Clone(path)) // Hit the target exactly. Save a copy.
return
}
for i := start; i < len(sorted); i++ {
if sorted[i] > remaining {
break // Sorted, so every later candidate is too big too.
}
path = append(path, sorted[i]) // Choose sorted[i].
backtrack(i, remaining-sorted[i]) // Explore from i, not i + 1: reuse is allowed.
path = path[:len(path)-1] // Un-choose.
}
}
backtrack(0, target)
return result
}
func main() {
fmt.Println(combinationSum([]int{2, 3, 6, 7}, 7)) // [[2 2 3] [7]]
fmt.Println(combinationSum([]int{2, 3, 5}, 8)) // [[2 2 2 2] [2 3 3] [3 5]]
fmt.Println(combinationSum([]int{2}, 1)) // []
}using System;
using System.Collections.Generic;
using System.Linq;
public class Solution
{
// Every unique combination of candidates that sums to target, where each
// candidate can be used any number of times. Each combination is ascending.
public static List<List<int>> CombinationSum(int[] candidates, int target)
{
var sorted = candidates.OrderBy(c => c).ToArray();
var result = new List<List<int>>();
var path = new List<int>();
void Backtrack(int start, int remaining)
{
if (remaining == 0)
{
result.Add(new List<int>(path)); // Hit the target exactly. Save a copy.
return;
}
for (int i = start; i < sorted.Length; i++)
{
if (sorted[i] > remaining) break; // Sorted, so every later candidate is too big too.
path.Add(sorted[i]); // Choose sorted[i].
Backtrack(i, remaining - sorted[i]); // Explore from i, not i + 1: reuse is allowed.
path.RemoveAt(path.Count - 1); // Un-choose.
}
}
Backtrack(0, target);
return result;
}
public static void Main()
{
Console.WriteLine(Format(CombinationSum(new[] { 2, 3, 6, 7 }, 7))); // [[2, 2, 3], [7]]
Console.WriteLine(Format(CombinationSum(new[] { 2, 3, 5 }, 8))); // [[2, 2, 2, 2], [2, 3, 3], [3, 5]]
Console.WriteLine(Format(CombinationSum(new[] { 2 }, 1))); // []
}
static string Format(List<List<int>> lists) =>
"[" + string.Join(", ", lists.Select(l => "[" + string.Join(", ", l) + "]")) + "]";
}import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
// Every unique combination of candidates that sums to target, where each
// candidate can be used any number of times. Each combination is ascending.
static List<List<Integer>> combinationSum(int[] candidates, int target) {
int[] sorted = candidates.clone();
Arrays.sort(sorted);
List<List<Integer>> result = new ArrayList<>();
backtrack(sorted, 0, target, new ArrayList<>(), result);
return result;
}
static void backtrack(int[] sorted, int start, int remaining, List<Integer> path,
List<List<Integer>> result) {
if (remaining == 0) {
result.add(new ArrayList<>(path)); // Hit the target exactly. Save a copy.
return;
}
for (int i = start; i < sorted.length; i++) {
if (sorted[i] > remaining) break; // Sorted, so every later candidate is too big too.
path.add(sorted[i]); // Choose sorted[i].
backtrack(sorted, i, remaining - sorted[i], path, result); // Explore from i, not i + 1: reuse is allowed.
path.remove(path.size() - 1); // Un-choose.
}
}
public static void main(String[] args) {
System.out.println(combinationSum(new int[] {2, 3, 6, 7}, 7)); // [[2, 2, 3], [7]]
System.out.println(combinationSum(new int[] {2, 3, 5}, 8)); // [[2, 2, 2, 2], [2, 3, 3], [3, 5]]
System.out.println(combinationSum(new int[] {2}, 1)); // []
}
}Problem
Place n queens on an n x n chessboard so that no two share a row, a column or a diagonal. Return every such arrangement, each as a list of row strings where Q is a queen and . is an empty square. n is at least 1.
Example: n = 4 has two solutions. The first is [".Q..", "...Q", "Q...", "..Q."], and the second is its mirror image. The classic n = 8 board has 92.
Show SolutionHide Solution
Approach
Two queens in the same row attack each other, so every row holds exactly one queen. That turns the search into one decision per row: which column does this row's queen go in? The tree is n levels deep, one level per row.
A square is safe when its column, its diagonal and its anti-diagonal are all free. Squares on one diagonal share the same row - col, and squares on one anti-diagonal share the same row + col, so three boolean arrays answer the check in O(1):
row - col row + col 0 -1 -2 -3 0 1 2 3 1 0 -1 -2 1 2 3 4 2 1 0 -1 2 3 4 5 3 2 1 0 3 4 5 6
Choose sets the three flags, explore moves to the next row, and un-choose clears the flags. queens[row] doesn't need undoing, because the next choice in that row overwrites it. Skipping attacked squares before recursing is the pruning. The column check alone leaves row r with at most n - r choices, so at most n! full placements are ever tried, against the n^n of trying every column in every row. The diagonal checks cut far deeper still, so O(n!) is a comfortable upper bound.
N-Queens on a 4 x 4 Board
Step 1 of 14
board
queens
- Queen just placed
- Earlier queens
- Attacked square
- Dead end
- Solution
Two queens in one row attack each other, so each row gets exactly one queen. The search decides one row at a time: which column does this row's queen go in?
All 14 steps as text
- Two queens in one row attack each other, so each row gets exactly one queen. The search decides one row at a time: which column does this row's queen go in?
- Row
0: column0is safe, so the queen goes there. The squares it attacks are ruled out in every row below, so the search never builds on them. That's the pruning. - Row
1: columns0, 1are attacked, so the queen goes in column2. - Every square in row
2is attacked, so no queen fits and this branch can't hold a solution. Go back to row1and try its next column. - Move row
1's queen on to column3, the next square in the row that isn't attacked. - Row
2: column0is attacked, so the queen goes in column1. - Every square in row
3is attacked, so no queen fits and this branch can't hold a solution. Go back to row2and try its next column. - Columns
2, 3of row2are attacked too, so the row is out of options. Un-choose it and go back to row1, which tries the columns after3. - Row
1's queen was already in the last column, so the row is out of options. Un-choose it and go back to row0, which tries the columns after0. - Move row
0's queen on to column1, the next square in the row that isn't attacked. - Row
1: columns0, 1, 2are attacked, so the queen goes in column3. - Row
2: column0is safe, so the queen goes there. - Row
3: columns0, 1are attacked, so the queen goes in column2. rowreached4, so every row has a queen. Save the board,[".Q..", "...Q", "Q...", "..Q."]. Searching on finds its mirror image, the second solution.
Time O(n!)Space O(n) beyond the output
def solve_n_queens(n: int) -> list[list[str]]:
"""Every way to place n queens on an n x n board so no two attack each other.
Each board is a list of rows, with "Q" for a queen and "." for an empty square."""
result: list[list[str]] = []
queens = [0] * n # queens[row] = column of that row's queen
cols = [False] * n
diags = [False] * (2 * n - 1) # Indexed by row - col + n - 1.
anti_diags = [False] * (2 * n - 1) # Indexed by row + col.
def backtrack(row: int) -> None:
if row == n:
result.append(["." * col + "Q" + "." * (n - col - 1) for col in queens])
return
for col in range(n):
d = row - col + n - 1
a = row + col
if cols[col] or diags[d] or anti_diags[a]:
continue # Attacked: prune this branch.
queens[row] = col # Choose: put this row's queen in col.
cols[col] = diags[d] = anti_diags[a] = True
backtrack(row + 1) # Explore the next row (one queen per row).
# Un-choose. The next choice overwrites queens[row], so only the flags need resetting.
cols[col] = diags[d] = anti_diags[a] = False
backtrack(0)
return result
if __name__ == "__main__":
boards = solve_n_queens(4)
print(len(boards)) # 2
for row in boards[0]:
print(row) # .Q.. then ...Q then Q... then ..Q.
print(len(solve_n_queens(8))) # 92// Every way to place n queens on an n x n board so no two attack each other.
// Each board is a list of rows, with "Q" for a queen and "." for an empty square.
export function solveNQueens(n: number): string[][] {
const result: string[][] = [];
const queens = new Array<number>(n).fill(0); // queens[row] = column of that row's queen
const cols = new Array<boolean>(n).fill(false);
const diags = new Array<boolean>(2 * n - 1).fill(false); // Indexed by row - col + n - 1.
const antiDiags = new Array<boolean>(2 * n - 1).fill(false); // Indexed by row + col.
function backtrack(row: number): void {
if (row === n) {
result.push(queens.map((col) => ".".repeat(col) + "Q" + ".".repeat(n - col - 1)));
return;
}
for (let col = 0; col < n; col++) {
const d = row - col + n - 1;
const a = row + col;
if (cols[col] || diags[d] || antiDiags[a]) continue; // Attacked: prune this branch.
queens[row] = col; // Choose: put this row's queen in col.
cols[col] = diags[d] = antiDiags[a] = true;
backtrack(row + 1); // Explore the next row (one queen per row).
// Un-choose. The next choice overwrites queens[row], so only the flags need resetting.
cols[col] = diags[d] = antiDiags[a] = false;
}
}
backtrack(0);
return result;
}
const boards = solveNQueens(4);
console.log(boards.length); // 2
for (const row of boards[0]) console.log(row); // .Q.. then ...Q then Q... then ..Q.
console.log(solveNQueens(8).length); // 92package main
import (
"fmt"
"strings"
)
// solveNQueens returns every way to place n queens on an n x n board so no two
// attack each other. Each board is a list of rows, with "Q" for a queen and "."
// for an empty square.
func solveNQueens(n int) [][]string {
result := [][]string{}
queens := make([]int, n) // queens[row] = column of that row's queen
cols := make([]bool, n)
diags := make([]bool, 2*n-1) // Indexed by row - col + n - 1.
antiDiags := make([]bool, 2*n-1) // Indexed by row + col.
var backtrack func(row int)
backtrack = func(row int) {
if row == n {
board := make([]string, n)
for r, col := range queens {
board[r] = strings.Repeat(".", col) + "Q" + strings.Repeat(".", n-col-1)
}
result = append(result, board)
return
}
for col := 0; col < n; col++ {
d := row - col + n - 1
a := row + col
if cols[col] || diags[d] || antiDiags[a] {
continue // Attacked: prune this branch.
}
queens[row] = col // Choose: put this row's queen in col.
cols[col], diags[d], antiDiags[a] = true, true, true
backtrack(row + 1) // Explore the next row (one queen per row).
// Un-choose. The next choice overwrites queens[row], so only the flags need resetting.
cols[col], diags[d], antiDiags[a] = false, false, false
}
}
backtrack(0)
return result
}
func main() {
boards := solveNQueens(4)
fmt.Println(len(boards)) // 2
for _, row := range boards[0] {
fmt.Println(row) // .Q.. then ...Q then Q... then ..Q.
}
fmt.Println(len(solveNQueens(8))) // 92
}using System;
using System.Collections.Generic;
using System.Linq;
public class Solution
{
// Every way to place n queens on an n x n board so no two attack each other.
// Each board is a list of rows, with "Q" for a queen and "." for an empty square.
public static List<List<string>> SolveNQueens(int n)
{
var result = new List<List<string>>();
var queens = new int[n]; // queens[row] = column of that row's queen
var cols = new bool[n];
var diags = new bool[2 * n - 1]; // Indexed by row - col + n - 1.
var antiDiags = new bool[2 * n - 1]; // Indexed by row + col.
void Backtrack(int row)
{
if (row == n)
{
result.Add(queens.Select(col => new string('.', col) + "Q" + new string('.', n - col - 1)).ToList());
return;
}
for (int col = 0; col < n; col++)
{
int d = row - col + n - 1;
int a = row + col;
if (cols[col] || diags[d] || antiDiags[a]) continue; // Attacked: prune this branch.
queens[row] = col; // Choose: put this row's queen in col.
cols[col] = diags[d] = antiDiags[a] = true;
Backtrack(row + 1); // Explore the next row (one queen per row).
// Un-choose. The next choice overwrites queens[row], so only the flags need resetting.
cols[col] = diags[d] = antiDiags[a] = false;
}
}
Backtrack(0);
return result;
}
public static void Main()
{
var boards = SolveNQueens(4);
Console.WriteLine(boards.Count); // 2
foreach (var row in boards[0]) Console.WriteLine(row); // .Q.. then ...Q then Q... then ..Q.
Console.WriteLine(SolveNQueens(8).Count); // 92
}
}import java.util.ArrayList;
import java.util.List;
public class Solution {
// Every way to place n queens on an n x n board so no two attack each other.
// Each board is a list of rows, with "Q" for a queen and "." for an empty square.
static List<List<String>> solveNQueens(int n) {
List<List<String>> result = new ArrayList<>();
int[] queens = new int[n]; // queens[row] = column of that row's queen
boolean[] cols = new boolean[n];
boolean[] diags = new boolean[2 * n - 1]; // Indexed by row - col + n - 1.
boolean[] antiDiags = new boolean[2 * n - 1]; // Indexed by row + col.
backtrack(0, queens, cols, diags, antiDiags, result);
return result;
}
static void backtrack(int row, int[] queens, boolean[] cols, boolean[] diags,
boolean[] antiDiags, List<List<String>> result) {
int n = queens.length;
if (row == n) {
List<String> board = new ArrayList<>();
for (int col : queens) board.add(".".repeat(col) + "Q" + ".".repeat(n - col - 1));
result.add(board);
return;
}
for (int col = 0; col < n; col++) {
int d = row - col + n - 1;
int a = row + col;
if (cols[col] || diags[d] || antiDiags[a]) continue; // Attacked: prune this branch.
queens[row] = col; // Choose: put this row's queen in col.
cols[col] = diags[d] = antiDiags[a] = true;
backtrack(row + 1, queens, cols, diags, antiDiags, result); // Explore the next row (one queen per row).
// Un-choose. The next choice overwrites queens[row], so only the flags need resetting.
cols[col] = diags[d] = antiDiags[a] = false;
}
}
public static void main(String[] args) {
List<List<String>> boards = solveNQueens(4);
System.out.println(boards.size()); // 2
for (String row : boards.get(0)) System.out.println(row); // .Q.. then ...Q then Q... then ..Q.
System.out.println(solveNQueens(8).size()); // 92
}
}Common Pitfalls
- Saving
pathitself instead of a copy. Every saved answer is then the same list, and it's empty by the time the search finishes. - Returning or continuing between choose and un-choose. The undo never runs, and the leftover state corrupts every branch explored after it.
- Using the wrong loop start. Starting at
0for combinations returns[1, 2]and[2, 1]as separate answers, and starting atstartfor permutations misses orderings. - Recursing with
i + 1when reuse is allowed (Combination Sum) or withiwhen it isn't (Combination Sum II). The first loses answers like[2, 2, 3], and the second uses one element twice. - Breaking out of the loop on a candidate that's too big without sorting first. On unsorted input, a smaller candidate can still come later.
Practice
More problems to try on your own.