Backtracking

Build answers one choice at a time: choose, explore, un-choose, and prune any branch that can't lead to a solution.

IntermediateRead first: Recursion

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:

  1. Choose: add an option to the current path, and mark it used if that matters.
  2. Explore: recurse to make the remaining choices.
  3. 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]
The tree the Subsets template walks for [1, 2, 3]. Each edge chooses one more number to the right of the last one. Walking down an edge is choose, walking back up is un-choose, and every node is a subset.

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
  1. 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.
  2. Choose 1: push it onto path and save a copy, [1]. Only numbers to the right of 1 are offered next: 2, 3.
  3. Choose 2: push it onto path and save a copy, [1, 2]. Only numbers to the right of 2 are offered next: 3.
  4. Choose 3: push it onto path and save a copy, [1, 2, 3]. Nothing lies to the right of 3, so the loop has no options and this node is a leaf.
  5. Un-choose 3: pop it, so path is [1, 2] again. That was the last option, so [1, 2] has no branches left to try.
  6. Un-choose 2: pop it, so path is [1] again. The loop moves on to 3, starting from that same path.
  7. Choose 3: push it onto path and save a copy, [1, 3]. Nothing lies to the right of 3, so the loop has no options and this node is a leaf.
  8. Un-choose 3: pop it, so path is [1] again. That was the last option, so [1] has no branches left to try.
  9. Un-choose 1: pop it, so path is empty again. The loop moves on to 2, starting from that same path.
  10. Choose 2: push it onto path and save a copy, [2]. Only 3 can follow it. [2, 1] is never built, because it would repeat [1, 2].
  11. Choose 3: push it onto path and save a copy, [2, 3]. Nothing lies to the right of 3, so the loop has no options and this node is a leaf.
  12. Un-choose 3: pop it, so path is [2] again. That was the last option, so [2] has no branches left to try.
  13. Un-choose 2: pop it, so path is empty again. The loop moves on to 3, starting from that same path.
  14. Choose 3: push it onto path and save a copy, [3]. Nothing lies to the right of 3, so the loop has no options and this node is a leaf.
  15. Un-choose 3: pop it, so path is empty again. That was the last option, so the root has no branches left to try.
  16. backtrack(0) returns and the search is over. result holds all 8 subsets, 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 start index 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 used array 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

OperationTimeSpace
Subsets of n itemsO(n · 2^n)O(n) beyond the output
Permutations of n itemsO(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 boardO(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 of path.
  • Explore: backtrack(i + 1) builds every subset that begins with the current path. Passing i + 1 means 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 of path again. 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.

Permutations

MEDIUMLeetCode 46

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 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]]
    }
}

Combination Sum

MEDIUMLeetCode 39

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 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)); // []
    }
}

N-Queens

HARDLeetCode 51

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 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
On a 4 x 4 board. Adding n - 1 to row - col shifts it into the range 0 to 2n - 2 so it can index an array.

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
  1. 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?
  2. Row 0: column 0 is 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.
  3. Row 1: columns 0, 1 are attacked, so the queen goes in column 2.
  4. Every square in row 2 is attacked, so no queen fits and this branch can't hold a solution. Go back to row 1 and try its next column.
  5. Move row 1's queen on to column 3, the next square in the row that isn't attacked.
  6. Row 2: column 0 is attacked, so the queen goes in column 1.
  7. Every square in row 3 is attacked, so no queen fits and this branch can't hold a solution. Go back to row 2 and try its next column.
  8. Columns 2, 3 of row 2 are attacked too, so the row is out of options. Un-choose it and go back to row 1, which tries the columns after 3.
  9. Row 1's queen was already in the last column, so the row is out of options. Un-choose it and go back to row 0, which tries the columns after 0.
  10. Move row 0's queen on to column 1, the next square in the row that isn't attacked.
  11. Row 1: columns 0, 1, 2 are attacked, so the queen goes in column 3.
  12. Row 2: column 0 is safe, so the queen goes there.
  13. Row 3: columns 0, 1 are attacked, so the queen goes in column 2.
  14. row reached 4, 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); // 92
package 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 path itself 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 0 for combinations returns [1, 2] and [2, 1] as separate answers, and starting at start for permutations misses orderings.
  • Recursing with i + 1 when reuse is allowed (Combination Sum) or with i when 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.