22
头图

1. Backtracking algorithm

1.1 What is backtracking?

The backtracking algorithm is actually a search trial process similar to enumeration, which is mainly to find the solution of the problem in the search trial process. When it is found that the solution condition is not satisfied, it will "backtrack" and return and try another path. ——Excerpt from "Baidu Encyclopedia"

1.1 General steps:

  1. For the given problem, define the solution space of the problem, which contains at least one (optimal) solution of the problem.
  2. Determine the solution space structure that is easy to search, so that the entire solution space can be searched easily by the backtracking method.
  3. Search the solution space in a depth-first manner, and use a pruning function to avoid invalid searches in the search process.

1.2 How to understand the backtracking algorithm?

  1. Establish a solution space structure for the problem
  2. DFS search on solution space structure
  3. Set up backtracking exits and pruning points to reduce invalid searches and save valid solutions at the exit.

1.3 What are the problems to be solved?

  1. Combination problem: Find the set of k numbers according to the rules of N numbers
  2. Cutting problem: There are several cutting methods for a string according to certain rules
  3. Subset problem: How many eligible subsets are there in a set of N numbers
  4. Arrangement problem: N numbers are arranged according to a certain rule, there are several arrangements
  5. Board problem: Queen N, solving Sudoku and so on.

1.4 Recursion and backtracking

First explain the understanding of recursive (Recursive) and backtracking (Backtrack).

1.4.1 Recursive

The programming technique of a program calling itself is called recursion.
Recursion as an algorithm is widely used in programming languages. A process or function has a method of directly or indirectly invoking itself in its definition or description. It usually transforms a large and complex problem into a smaller problem similar to the original problem to solve. The recursive strategy is only A small amount of programs can describe the multiple repetitive calculations required in the process of solving the problem, which greatly reduces the amount of code of the program. ——Excerpt from "Baidu Encyclopedia"

Generally speaking, in order to describe a certain state of the problem, the previous state of the state must be used; and if the previous state is to be described, the previous state of the previous state must be used... In this way, use yourself to define your own The method is recursion.

1.4.2. Backtrack

The backtracking algorithm is actually a search trial process similar to enumeration, which is mainly to find the solution of the problem in the search trial process. When it is found that the solution condition is not satisfied, it will "backtrack" and return and try another path. ——Excerpt from "Baidu Encyclopedia"

Under this kind of thinking, we need to clearly identify three elements: Options, Restraints, and Termination.

1.5. The difference between recursion and backtracking

Recursion is an algorithm structure. Recursion will appear in subroutines, in the form of calling itself directly or indirectly. A typical example is factorial, and the calculation rule is: n!=n×(n−1)!n!=n \times (n-1)!, basically as follows:

let fac = (n)=> {
    if(n == 1){
       return n;
    }else{
      return (n*fac(n - 1)); 
    }    
}

Backtracking is an algorithmic idea, which is implemented by recursion. The process of backtracking is similar to the exhaustive method, but backtracking has the function of "pruning", that is, the process of self-judgment.

Second, Leetcode backtracking problem

2.1- 22. Bracket generation

The number n represents the logarithm of generating brackets. Please design a function to generate all possible and effective bracket combinations.

Example 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

Example 2:

输入:n = 1
输出:["()"]

hint:
1 <= n <= 8

Thinking analysis

  1. Judging the number of left and right parentheses, the initial value is n; when the left parenthesis (() is left, continue to make choices;
  2. Only when there are more right parentheses than left parentheses can the right parentheses be selected; continue to make selections recursively
  3. Export: When the constructed string is 2n, the branch has been constructed at this time, and the option is added;

Short answer drawing graphics

Problem-solving code

var generateParenthesis = function (n) {
    const res = [];
    const backTracing = (lRemain, rRemain, str) => { // 左右括号所剩的数量,str是当前构建的字符串
        if (str.length == 2 * n) { // 字符串构建完成
            res.push(str);           // 加入解集
            return;                  // 结束当前递归分支
        }
        if (lRemain > 0) {         // 只要左括号有剩,就可以选它,然后继续做选择(递归)
            backTracing(lRemain - 1, rRemain, str + "(");
        }
        if (lRemain < rRemain) {   // 右括号比左括号剩的多,才能选右括号
            backTracing(lRemain, rRemain - 1, str + ")"); // 然后继续做选择(递归)
        }
    };
    backTracing(n, n, ""); // 递归的入口,剩余数量都是n,初始字符串是空串
    return res;
};

2.2-46. Full arrangement

Given an array nums without repeated numbers, return all possible permutations. You can return the answers in any order.

Example 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

Example 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

Example 3:

输入:nums = [1]
输出:[[1]]

hint:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
All integers in nums are different from each other

Problem-solving ideas

  1. Backtracking termination condition: the length of the path and the length of nums;
  2. Add the current value to the path. If the path is already included in the result, do not add it to the result, otherwise continue to select this option;

Problem-solving code

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permute = function (nums) {
    if (!nums.length) return
    let res = []
    let backTrack = path => {
        //长度满足条件,加入结果
        if (path.length === nums.length) {
            res.push(path)
            return
        }
        nums.forEach(item => {
            if (path.includes(item)) return //不包含重复的数字
            backTrack([...path, item]) //加入路径,继续递归选择
        });
    }
    backTrack([])
    return res
};

[Image upload failed...(image-40cdd5-1639281547994)]

2.3-n queen problem

The research is how to place n queens on an n × n chessboard, and make the queens unable to attack each other.

Give you an integer n, and return the number of different solutions to the n queen problem. *

image

Queen's rules

The Queen's way of walking is: you can walk straight and diagonally, and there is no limit to the number of grids. Therefore, requiring queens not to attack each other is equivalent to requiring no two queens to be in the same row, column, and diagonal line.

Example

Example 1:
image

输入:n = 4
输出:2

Explanation: As shown in the figure above, there are two different solutions to the 4-queen problem.

Example 2:

输入:n = 1
输出:1

hint:
1 <= n <= 9

Problem-solving ideas

  1. Define the test function for judging the current position. The constraint conditions include: cannot be in the same row, cannot be in the same column, and cannot be in the same diagonal (45 degrees and 135 degrees)
  2. Define the chessboard; standard backtracking processing;

The specific method of using backtracking is to place a queen in each row in turn. Each time the newly placed queen cannot attack the placed queen, that is, the newly placed queen cannot be in the same column as any placed queen. The same diagonal line. When NNN queens are placed, a possible solution is found, and the number of possible solutions is added to 111.

image source

Problem-solving code

var totalNQueens = function (n) {
    let count = 0; //皇后可放置的总数
    let isValid = (row, col, board, n) => {
        //所在行不用判断,每次都会下移一行
        //判断同一列的数据是否包含
        for (let i = 0; i < row; i++) {
            if (board[i][col] === 'Q') {
                return false
            }
        }
        //判断45度对角线是否包含
        for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if (board[i][j] === 'Q') {
                return false
            }
        }
        //判断135度对角线是否包含
        for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; j--, i--) {
            if (board[i][j] === 'Q') {
                return false
            }
        }
        return true
    }

    let backTracing = (row, board) => {
        //走到最后一行,统计次数
        if (row === n) {
            count++;
            return
        }

        for (let x = 0; x < n; x++) {
            //判断该位置是否可以放置 皇后
            if (isValid(row, x, board, n)) {
                board[row][x] = 'Q'; //放置皇后
                backTracing(row + 1, board); //递归
                board[row][x] = '.'; //回溯,撤销处理结果
            }
        }
    }
    backTracing(0, board)
    let board = [...Array(n)].map(v => v = ([...Array(n)]).fill('.')) //棋盘
    return count
};

2.4-78. Subset

Give you an integer array nums, the elements in the array are different from each other. Return all possible subsets (power sets) of the array.

The solution set cannot contain duplicate subsets. You can return the solution set in any order.

Example 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

Example 2:

输入:nums = [0]
输出:[[],[0]]

hint:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
All elements in nums are different from each other

Problem-solving ideas

  1. Enumerate all the optional numbers; add options;
  2. Cancel the added option, add the option to the result

Problem-solving code

const subsets = (nums) => {
    const res = [];
    const backTracing = (index, list) => {
        res.push(list.slice());     // 调用子递归前,加入解集
        for (let i = index; i < nums.length; i++) { // 枚举出所有可选的数
            list.push(nums[i]);       // 选这个数
            backTracing(i + 1, list);         // 基于选这个数,继续递归
            list.pop();               // 撤销选这个数
        }
    };
    backTracing(0, []);
    return res;
};

2.5-77. Combination

Given two integers n and k, return all possible combinations of k numbers in the range [1, n].

You can return the answers in any order.

Example 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

Example 2:

输入:n = 1, k = 1
输出:[[1]]

hint:

1 <= n <= 20
1 <= k <= n

Problem-solving ideas

  1. Enumerate all the optional numbers; add options;
  2. Cancel the added option, add the option to the result
  3. Pruning condition: the length of the option satisfies the condition;

Problem-solving code

/**
 * @param {number} n
 * @param {number} k
 * @return {number[][]}
 */
var combine = function (n, k) {
    let result = [];
    let backTracing = (start, path) => {
        // 如果已经选满了的话,加入结果集中
        if (path.length == k) {
            result.push(path.slice());
            return;
        }
        // 从开始的数字到末尾的数字
        for (let i = start; i <= n; i++) {
            // 加入选择列表中
            path.push(i);
            // 继续深度搜索
            backTracing(i + 1, path);
            // 撤销选择
            path.pop(i);
        }
    };
    backTracing(1, []);
    return result;
};

2.6-081. Allow repeated selection of combinations of elements

Given a positive integer array candidates with no repeated elements and a positive integer target, find out all the unique combinations of candidates that can make the number sum the target number target.

The numbers in candidates can be repeatedly selected without limitation. If at least one of the selected numbers differs in number, the two combinations are unique.
For a given input, it is guaranteed that the number of unique combinations whose sum is the target is less than 150.

Example 1:

输入: candidates = [2,3,6,7], target = 7
输出: [[7],[2,2,3]]

Example 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

Example 3:

输入: candidates = [2], target = 1
输出: []

Example 4:

输入: candidates = [1], target = 1
输出: [[1]]

Example 5:

输入: candidates = [1], target = 2
输出: [[1,1]]

hint:
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
Each element in a candidate is unique.
1 <= target <= 500

Problem-solving ideas

  1. Add the current element to the option, and pass the calculation result to the option, and continue recursion;
  2. When the option sum is greater than the target value, end this option until a matching option is found, and the option is added to the result;

Problem-solving code

var combinationSum = function (candidates, target) {
    const result = [], visited = [];
    backTracing(0, 0);
    return result;

    function backTracing(sum, cur) {
        if (target === sum) result.push(visited.slice());
        if (target <= sum) return;
        for (let i = cur; i < candidates.length; i++) {
            visited.push(candidates[i]); //加入到选项里面
            backTracing(sum + candidates[i], i);//选择这个选项,继续递归
            visited.pop(); //插销这个选择
        }
    }
};

2.7-216. Combination Sum III

Find all the combinations of k numbers that add up to n. Only positive integers from 1 to 9 are allowed in the combination, and there are no repeated numbers in each combination.

instruction:
All numbers are positive integers.
The solution set cannot contain repeated combinations.

Example 1:

输入: k = 3, n = 7
输出: [[1,2,4]]

Example 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

Problem-solving ideas

Same combination 1

Problem-solving code

var combinationSum3 = function (k, n) {
    let ans = [];
    let backTracing = (start, path) => {
        if (path.length === k && path.reduce((acc, prev) => acc += prev) === n) {
            ans.push(path.slice())
            return
        }
        for (let i = start; i <= 9; i++) {
            path.push(i)
            backTracing(i + 1, path)
            path.pop(i)
        }
    }
    backTracing(1, [])
    return ans
};

2.8-17. Letter combination of phone number

Given a string containing only numbers 2-9, return all letter combinations it can represent. The answers can be returned in any order.

The mapping of numbers to letters is given as follows (same as phone buttons). Note that 1 does not correspond to any letters.

Example 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

Example 2:

输入:digits = ""
输出:[]

Example 3:

输入:digits = "2"
输出:["a","b","c"]

hint:
0 <= digits.length <= 4
digits[i] is a number in the range ['2', '9'].

Problem-solving ideas

  1. Find the letter string corresponding to the current button
  2. The string combination corresponding to the splicing button
  3. Option meets the length, join the result

Problem-solving code

var letterCombinations = function (digits) {
    if(!digits.length) return []
    const dic = {
        2: 'abc',
        3: 'def',
        4: 'ghi',
        5: 'jkl',
        6: 'mno',
        7: 'pqrs',
        8: 'tuv',
        9: 'wxyz',
    }, ans = [];

    let backTracing = (cur, index) => {
        if (index > digits.length - 1) { //选项满足长度,加入结果
            ans.push(cur)
            return
        }
        let curDic = dic[digits[index]] //找到当前按钮对应的字母字符串
        for (let prop of curDic) {
            backTracing(cur + prop,index+1) //拼接按钮对应的字符串组合
        }
    }
    backTracing('', 0)
    return ans
};

2.9-08.01. Three-step problem

Three-step question. There is a child who is going up the stairs. The stairs have n steps. The child can go up 1, 2, or 3 steps at a time. Implement a method to calculate how many ways the child has to go up the stairs. The result may be very large, you need to modulo 1000000007 to the result.

Example 1:

 输入:n = 3 
 输出:4
 说明: 有四种走法

Example 2:

 输入:n = 5
 输出:13

hint:
n range between [1, 1000000]

Problem-solving code (will time out)

 var waysToStep = function (n) {
    let ans = [], map = [1, 2, 3]
    let backTracing = (path, sum) => {
        if (sum >= n) {
            if (sum == n) {
                ans++;
            }
            return
        }
        for (let i = 0; i < 3; i++) {
            path.push(map[i]);
            backTracing(path, sum + map[i])
            path.pop(i)
        }
    }
    backTracing([], 0)
    return ans
};

Dynamic programming solution

/**
 * @param {number} n
 * @return {number}
 */
var waysToStep = function (n) {
    let dp =[],mod = 1000000007;
    dp[0]=0,dp[1]=1,dp[2]=2,dp[3]=4;
    for (let i = 4; i <= n; i++) {
        dp[i] = (dp[i - 1] + dp[i - 2] + dp[i - 3]) % mod
    }
    return dp[n]
};

2-10-40. Combination Sum II

Given an array of candidates and a target number target, find out all combinations of candidates that can make the sum of the numbers target.

Each number in candidates can only be used once in each combination.
Note: The solution set cannot contain repeated combinations.

Example 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

Example 2:

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]

hint:
1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30

Problem-solving ideas

Same idea as combination 1

Problem-solving code


/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
 var combinationSum2 = function (candidates, target) {
    candidates.sort((a,b)=>a-b)
    let ans = [];
    let backTracing = (start, path, sum) => {
        if (sum >= target) {
            if (sum === target) {
                ans.push(path.slice())
            }
            return
        }
        for (let i = start; i < candidates.length; i++) {
            if (i - 1 >= start && candidates[i - 1] == candidates[i]) {
                continue;
            }
            path.push(candidates[i])
            backTracing(i + 1, path, sum + candidates[i])
            path.pop()
        }
    }
    backTracing(0, [], 0)
    return ans
};

2-11-47. Full Permutation II

Given a sequence nums that can contain repeated numbers, return all non-repeated permutations in any order.

Example 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
 [1,2,1],
 [2,1,1]]

Example 2:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

hint:
1 <= nums.length <= 8
-10 <= nums[i] <= 10

## Problem-solving ideas
Same as above

## Problem-solving code

var permuteUnique = function (nums) {
   let ans = [];
   let used = Array(nums.length).fill(false)
   let backTracing = (start, path) => {
       if (start === nums.length) {
           ans.push(path.slice())
           return
       }
       for (let i = 0; i < nums.length; ++i) {
           if (used[i] || (i > 0 && nums[i] === nums[i - 1] && !used[i - 1])) {
               continue;
           }
           path.push(nums[i])
           used[i] = true
           backTracing(start + 1, path)
           used[i] = false
           path.pop()
       }
   }
   nums.sort((a, b) => a - b)
   backTracing(0, [])
   return ans

};

Three, summary

The backtracking algorithm is mainly used; and solving a backtracking problem is actually a traversal process of a decision tree.

3.1 Template

let backtracking=(路径,选择列表) =>{
    if (满足结束条件)) {
        存放路径;
        return;
    }
    for (选择:路径,选择列表) {
        做出选择;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

which is:

  1. 1. Path: The choice that has been made.
  2. 2. Selection list: that is, the choices you can make currently.
  3. 3. End condition: that is, the condition that reaches the bottom of the decision tree and can no longer make a choice.

3.2 Pruning function

  1. 1. Prune the subtrees of unobtainable feasible solutions with constraints
  2. 2. Use the objective function to cut the subtree of the optimal solution that cannot be obtained

3.3 The general steps of the backtracking method:

  1. 1. Set up the initialization scheme (assign initial values to variables, read in known data, etc.)
  2. 2. Change the way to test, if all the tests are finished turning sideways (7)
  3. 3. Judge whether this method is successful (through the constraint function), if it is unsuccessful, go to (2)
  4. 4. If the temptation is successful, go one step further and try again
  5. 5. If the correct solution is still not found, then go to (2)
  6. 6. To find a solution, record and print
  7. 7. Go back one step (backtracking), if not back to the end, go to (2)
  8. 8. If it has been retracted to the end, it will end or print without solution

Keep going! ! !

# Four. References

  1. LeetCode Brushing Notes-Understanding of Recursion and Backtracking LeetCode Brushing Notes-Understanding of Recursion and Backtracking
  1. Graphical backtracking algorithm Graphical backtracking algorithm
  2. Backtracking algorithm summary Backtracking algorithm summary

微芒不朽
1.2k 声望1.3k 粉丝