Backtracking learning

Backtracking learning

  • First, what is backtracking?

    Baidu Encyclopedia defines it this way:

    Backtracking algorithm is actually a search attempt process similar to enumeration. It is mainly to find the solution of the problem in the search attempt process. When it is found that the solution conditions are not met, it will "backtrack" back and try other paths. Backtracking method is an optimization search method, which searches forward according to the optimization conditions to achieve the goal. However, when a certain step is explored and it is found that the original selection is not excellent or fails to achieve the goal, it will go back to one step and reselect. This technology of going back and going again if it fails is the backtracking method, and the point in a certain state that meets the backtracking conditions is called the "backtracking point". Many complex and large-scale problems can use backtracking method, which is known as "general problem-solving method".

    Backtracking algorithm is actually a process of continuous exploration and attempt. If the exploration is successful, it will be successful. If the exploration fails, we will step back and continue to try

  • Look at a topic

    Combinatorial summation is a classic backtracking algorithm problem, one of which is like this.

    Find the combination of all k numbers whose sum is n. Only positive integers of 1 - 9 are allowed in the combination, and there are no duplicate numbers in each combination.

    explain:

    • All numbers are positive integers.
    • The solution set cannot contain duplicate combinations.

    Example 1:

    Input: k = 3, n = 7

    Output: [[1,2,4]]

    Example 2:

    Input: k = 3, n = 9

    Output: [[1,2,6], [1,3,5], [2,3,4]]

    This question is very clear, that is, choose k numbers from 1-9, and their sum is equal to n, and these K numbers cannot be repeated. Therefore, you can choose one of these 9 numbers for the first selection and go down this branch. You can also choose one of these 9 numbers for the second selection, but because there can be no repetition, you should exclude the first selection. For the second selection, you can only choose one of the remaining 8 numbers. The selection process is relatively clear. We can regard it as a 9-fork tree. Except for leaf nodes, each node has 9 child nodes, which is as follows:

Choose one of the nine numbers and go all the way along any branch. In fact, it is DFS. Here you can write a 9-ary tree following the DFS of a binary tree. The DFS of a 9-ary tree traverses its 9 child nodes recursively. The code is as follows:

public static void treeDFS(TreeNode root){
    //The first is the termination condition of recursion
    if(root == null) 
        return;
    System.out.println(root.val);
    
    //Then through the loop, traverse 9 subtrees respectively
    for(int i = 1; i<=9; i++){
        //Here are some operations, optional, depending on the situation
        treeDFS("First child node");
        //There are also some operations here. They are optional. It depends on the situation
    }
}

DFS is actually equivalent to traversing all its branches, which is to list all its possible results. As long as the judgment result is equal to n, which is what we are looking for, how many layers does this 9-fork tree have at most? Because there are k numbers, there can only be K layers at most. code:

public List<List<Integer>> combinationSum3(int k, int n) {
    List<List<Integer>> res = new ArrayList<>();
    dfs(res, new ArrayList<>(), k, 1, n);
    return res;
}

private void dfs(List<List<Integer>> res, List<Integer> list, int k, int start, int n) {
    //Termination condition. If this condition is met, there is no point in looking down
    if (list.size() == k || n <= 0) {
        //If you find a suitable set, add it to the set list
        if (list.size() == k && n == 0)
            res.add(new ArrayList<>(list));
        return;
    }
    //Through the loop, traverse 9 subtrees respectively
    for (int i = start; i <= 9; i++) {
        //Select current value
        list.add(i);
        //recursion
        dfs(res, list, k, i + 1, n - i);
        //Undo selection
        list.remove(list.size() - 1);
    }
}
  • The first place in dfs code is the judgment of termination conditions. Recursion must have termination conditions.

  • The following for loop traverses his 9 child nodes respectively. Note that i here starts from start, so there may not be 9 child trees. As mentioned earlier, if you select one of the 9 numbers, you can only select the remaining 8 for the second time and the remaining 7 for the third time

  • In line 20, the start in dsf is i+1. Here's why it is i+1. For example, if I choose 3, I should start from 4 next time. If I don't add 1, there will be repeated numbers from 3 next time, which is obviously inconsistent with the requirements in the question

  • Why can't the i of the for loop start from 1 every time? If it starts from 1 every time, the result will be repeated. For example, if [1,2] is selected, it may be selected next time [2,1].

  • If you don't understand the backtracking algorithm, the last line may be the most difficult to understand. Why do you want to cancel the selection. If I often see my official account, I may know that, that is, the branch pollution problem I often mentioned, because list is a reference transfer. When we jump from one branch to another, if we do not remove the data from the previous branch, then list will take the data from the previous branch to the next branch, causing the result error. In addition to removing the data of the previous branch, another way is to create a list for each branch, so that each branch is a new list and does not interfere with each other, as shown in the figure below

The code is as follows

 public List<List<Integer>> combinationSum3(int k, int n) {
     List<List<Integer>> res = new ArrayList<>();
     dfs(res, new ArrayList<>(), k, 1, n);
     return res;
 }
 
 private void dfs(List<List<Integer>> res, List<Integer> list, int k, int start, int n) {
    //Termination condition. If this condition is met, there is no point in looking down
    if (list.size() == k || n <= 0) {
       //If you find a suitable set, add it to the set list
       if (list.size() == k && n == 0)
           res.add(new ArrayList<>(list));
       return;
   }
    //Through the loop, traverse 9 subtrees respectively
    for (int i = start; i <= 9; i++) {
       //Create a new list and get rid of the relationship with the original list. The modification of the current list will not affect the previous list
        List<Integer> subList = new LinkedList<>(list);
        subList.add(i);
        //recursion
        dfs(res, subList, k, i + 1, n - i);
        //Note that there is no undo operation here, because it is a modification in a new list, and the original list has not been modified,
        //Therefore, there is no need to undo the operation
    }
}

Finally, there is no undo operation, because each branch is a new list. Your modifications to the current branch will not affect other branches, so you don't need to undo the operation.

Although this method can also be solved, each branch will recreate the list, which is very inefficient.

To understand the last line of code, you must first understand what recursion is. Recursion is divided into two parts: recursion and return. Delivery is to pass down and return is to go back. Recursion where you call from and where you will eventually return. Draw a simple diagram and have a look

This is a very simple 3-ary tree. If you want to traverse it DFS, when you go down the path 1 → 2, the list should be [1, 2]. Because it is a recursive call, it will eventually return to node 1. If 2 is not removed, it will become [1, 2, 4] when walking along the branch 1 → 4, but in fact, the result of the branch 1 → 4 should be [1, 4], because we brought the value of the previous branch. After traversing the two nodes 1 and 2, it finally returns to node 1. When it returns to node 1, it should remove the value of node 2, change the list to [1], and then go down the branch 1 → 4. The result is [1, 4].

Summarize the code template of backtracking algorithm:

 private void backtrack("Original parameters") {
    //Termination condition (recursion must have termination condition)
    if ("Termination conditions") {
        //Some logical operations (optional, depending on the situation)
        return;
    }

    for (int i = "for Parameters for cycle start"; i < "for End of loop parameters"; i++) {
        //Some logical operations (optional, depending on the situation)

        //Make a choice

        //recursion
        backtrack("New parameters");
        //Some logical operations (optional, depending on the situation)

        //Undo selection
    }
}

Eight queens problem

The n queen problem studies how to place n queens in n × N's chessboard, and the Queens can't attack each other.

Give you an integer n and return the solution of all different N Queen problems.

Each solution contains a different chess placement scheme for the n-queen problem, in which 'Q' and '.' represent the queen and the vacancy respectively.

Example 1:

Input: n = 4
Output: [". Q..", "... Q", "q...,".. Q.], [". Q.", "q...,"... Q ",". Q..]]
Explanation: as shown in the figure above, there are two different solutions to the 4 queen problem.

Example 2:

Input: n = 1
Output: ["Q"]]

Tips:

1 <= n <= 9
Queens cannot attack each other, that is, no two Queens can be in the same horizontal, vertical or diagonal line.

Idea: apply template

code:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Solution {
    public static List<List<String>> solveNQueens(int n) {
        char[][] board = new char[n][n];//Initialize chessboard
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                board[i][j] = '.';
            }
        }
        List<List<String>> res = new ArrayList<>();
        dfs(board, res, 0);
        return res;
    }

    public static void dfs(char[][] board, List<List<String>> res, int col) {
        //Recursive termination condition
        if (col == board.length) {
            res.add(toList(board));
        }
        for (int i = 0; i < board.length; i++) {
            if (isValid(board, i, col)) {
                //Make a choice
                board[i][col] = 'Q';
                //Go to the next decision column
                dfs(board, res, col + 1);
                //Undo selection
                board[i][col] = '.';
            }
        }
    }

    //Convert 2D array to list
    private static List<String> toList(char[][] board) {
        List<String> res = new LinkedList<>();
        for (int i = 0; i < board.length; i++) {
            res.add(new String(board[i]));
        }
        return res;
    }

    //Judge whether the location is legal
    private static boolean isValid(char[][] board, int x, int y) {
        for (int i = 0; i < board.length; i++) {
            // Judge whether column j is placed in conflict with the previous one,
            // It is not necessary to judge y == j (cycle J < y), but to compare with the previous one
            for (int j = 0; j < y; j++) {
                if (board[i][j] == 'Q' && (x - y == i - j || x + y == i + j || x == i))
                    return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        List<List<String>> lists = solveNQueens(8);
        for (int i = 0; i < lists.size(); i++) {
            for (int j = 0; j < lists.get(i).size(); j++) {
                System.out.println(lists.get(i).get(j));
            }
            System.out.println();
        }
    }
}

Tags: Algorithm

Posted on Wed, 01 Dec 2021 04:29:45 -0500 by beaux1