Mike's avatar Mike's Blog

Sep 24, 2024

Template for Backtracking

What is backtracking?

Backtracking is a general algorithm for finding all (or some) solutions to some computational problems, notably constraint satisfaction problems, that incrementally builds candidates to the solutions, and abandons a candidate (“backtracks”) as soon as it determines that the candidate cannot possibly be completed to a valid solution.

Essentially, backtracking is dfs on a tree of possible solutions.

Backtracking template

For a general backtracking, we need to write a base case for the end condition, a loop for the choices, and a recursive call.

def backtrack(path, choices):
    if end_condition(path):
        res.append(path)
        return
    for choice in choices:
        if is_valid(path, choice):
            path.add(choice)
            backtrack(path, choices)
            path.remove(choice)

You can remove the path.add(choice) and path.remove(choice) if you just add the choice to the path in the recursive call.

backtrack(path + [choice], choices)

There are many variations of backtracking, such as the subset sum problem, n-queens problem, and sudoku.

Variations

We can moidy the template to fit different problems.

Permutations

For permutations, we need to add a visited set to keep track of the visited elements.

def backtrack(path, choices, visited):
    if end_condition(path):
        res.append(path)
        return
    for choice in choices:
        if is_valid(path, choice):
            path.add(choice)
            visited.add(choice)
            backtrack(path, choices, visited)
            path.remove(choice)
            visited.remove(choice)

Combinations

For combinations, we need to add a start index to avoid duplicates.

def backtrack(path, choices, start):
    if end_condition(path):
        res.append(path)
        return
    for i in range(start, len(choices)):
        if is_valid(path, choices[i]):
            path.add(choices[i])
            backtrack(path, choices, i + 1)
            path.remove(choices[i])

Duplicated input

For duplicated input, we need to sort the input and skip the duplicated elements.

def backtrack(path, choices, start):
    if end_condition(path):
        res.append(path)
        return
    for i in range(start, len(choices)):
        if i > start and choices[i] == choices[i - 1]:
            continue
        if is_valid(path, choices[i]):
            path.add(choices[i])
            backtrack(path, choices, i + 1)
            path.remove(choices[i])

Multiple solutions on one path(Subsets)

For subsets, we need to add the result to the result list, and we do not need to check the end condition.

def backtrack(path, choices, start):
    result.add(path)
    for i in range(start, len(choices)):
        if is_valid(path, choices[i]):
            path.add(choices[i])
            backtrack(path, choices, i + 1, result)
            path.remove(choices[i])