2
头图

Recursion

The relationship between recursion and backtracking is inseparable:
The basic nature of recursion is function call. When dealing with problems, recursion is often a process of making a large-scale problem smaller and then deriving it.
Backtracking is the process of making use of the nature of recursion, starting from the starting point of the problem, constantly trying, going back one step or even multiple steps to make choices, until the final destination is reached.

Recursive algorithm ideas

A recursive algorithm is an algorithm that calls its own function (many properties of a binary tree satisfy recursion by definition).

Tower of Hanoi

There are three towers A , B , C . At the beginning, n plates were placed A , and they were stacked from the bottom to the smallest. The tower now requires A in all the plates to move the tower C on, let you print out the handling steps. In the process of transport, only one plate can be transported at a time. In addition, at any time, no matter which tower it is in, the large plate cannot be placed on top of the small plate.

hano

Tower of Hanoi problem solution

  • Starting from the final result, if you want to n plates on the tower C in order of size, you need to A the largest plate at the bottom of the tower C to the tower 06192f48f0c5e0;
  • In order to achieve step 1 B of the plates except this largest plate on the tower 06192f48f0c60f.

It can be seen from the above that the original problem size is changed from n plates to n-1 plates, that is, n-1 plates are transferred to the tower B .

If a function is used, n plates can be moved from tower A B with the help of tower C . Then, you can also use this function to move the n-1 plates from the tower A C with the help of the tower B . In the same way, the problem scale is constantly reduced. When n is 1 , that is, there are only 1 plates, the steps are printed directly.

Tower of Hanoi problem code example

// 汉诺塔问题
const hano = function (A, B, C, n) {
  if (n > 0) {
    hano(A, C, B, n - 1)
    move(A, C)
    hano(B, A, C, n - 1)
  }
}

const move = function (p, c) {
  const temp = p.pop()
  c.push(temp)
}
const a = [1, 2, 3, 4, 5]
const b = []
const c = []
hano(a, b, c, a.length)

console.log('----after----')
console.log('a: ', String(a)) //
console.log('b: ', String(b)) //
console.log('c: ', String(c)) // 1, 2, 3, 4, 5

From the above, the recursive algorithm idea is summarized, the scale of a problem is reduced, and then the results obtained from the small-scale problem are used, combined with the current value or situation, to obtain the final result.

In layman's terms, the recursive function to be implemented is regarded as already implemented, and directly used to solve some sub-problems, and then what needs to be considered is how to get the answer based on the solution of the sub-problem and the current situation. This algorithm is also called a top-down ( Top-Down ) algorithm.

Digital decoding problem

LeetCode question 91, the method of decoding.
A message containing the letters AZ is encoded in the following way:
'A' -> 1
'B' -> 2

'Z' -> 26
Given a non-empty string containing only numbers, please count the total number of decoding methods.

Digital decoding problem solving ideas

  • As for the second example in the sample question, given the encoded message is the string "226", if there are m possibilities for decoding "22", then adding an additional "6" at the end is equivalent to the final There is only one more "F" character in the decrypted string, and the overall decoding is still only m types.
  • For "6", if it is preceded by "1" or "2", then it may be "16" or "26", so you can look forward to a character and find that it is "26" . Decoding the foregoing combination is k one, then this k a solution in an encoded, add a "Z", so that the total number of decoded m+k .

Digital decoding code implementation

const numDecoding = function (str) {
  if (str.charAt(0) === '0') return 0
  const chars = [...str]
  return decode(chars, chars.length - 1)
}

// 字符串转化成字符组,利用递归函数 decode,从最后一个字符串向前递归
const decode = function (chars, index) {
  if (index <= 0) return 1
  let count = 0
  let curr = chars[index]
  let prev = chars[index - 1]

  // 当前字符比 `0` 大,则直接利用它之前的字符串所求得结果
  if (curr > '0') {
    count = decode(chars, index - 1)
  }

  // 由前一个字符和当前字符构成的数字,值必须要在1和26之间,否则无法编码
  if (prev === '1' || (prev == '2' && curr <= '6')) {
    count += decode(chars, index - 2)
  }

  return count
}

console.log('count: ', numDecoding('1213')) // count: 5

Recursive problem solving template

Through the above example, to summarize and summarize the problem-solving template of the recursive function.

Problem solving steps

  • Determine whether the current situation is illegal, and return immediately if it is illegal. This step is also called an integrity check ( Sanity Check ). For example, check whether the current processing situation is out of bounds, and whether there is a situation that does not meet the conditions. Usually, this part of the code is written at the top.
  • Determine whether the conditions for ending recursion are met. In this step, the processing is basically the initial situation defined in the derivation process.
  • Reduce the size of the problem and call it recursively. In the merge sort and quick sort, we reduced the size of the problem by half, and in the example of the Tower of Hanoi and decoding, we reduced the size of the problem by one.
  • Use the answers in small-scale questions and integrate with current data to get the final answer.

Recursive problem solving template code implementation

function fn(n) {
    // 第一步:判断输入或者状态是否非法?
    if (input/state is invalid) {
        return;
    }

    // 第二步:判读递归是否应当结束?
    if (match condition) {
        return some value;
    }

    // 第三步:缩小问题规模
    result1 = fn(n1)
    result2 = fn(n2)
    ...

    // 第四步: 整合结果
    return combine(result1, result2)
}

Central symmetry problem

LeetCode Question 247: Find all the central symmetry numbers of n

Example
Input: n = 2
Output: ["11","69","88","96"]

Ideas for Solving Problems of Central Symmetric Numbers

symmetric-number

  • When n=0 , it should output an empty string: “ ” .
  • When n=1 time, i.e. the length of 1 center of symmetry number has: 0,1,8 .
  • When n=2 time, length 2 center of symmetry number has: 11, 69,88,96 . Note: 00 not a legal result.
  • When n=3 , only need 11,69,88,96 on both sides on the basis of the legal central symmetry number of 1

[101, 609, 808, 906, 111, 619, 818, 916, 181, 689, 888, 986]

With n continues to grow, we only need a length of n-2 symmetrical on both sides add the number of centers 11,69,88,96 can be.

Code implementation of central symmetry problem

const helper = function (n) {
  debugger
  // 第一步:判断输入或状态是否非法
  if (n < 0) {
    throw new Error('illegal argument')
  }

  // 第二步:判读递归是否应当结束
  if (n === 0) {
    return ['']
  }
  if (n === 1) {
    return ['0', '1', '8']
  }

  // 第三步:缩小问题规模
  const list = helper(n - 2)

  // 第四步:整合结果
  const res = []

  for (let i = 0; i < list.length; i++) {
    let s = list[i]
    res.push('1' + s + '1')
    res.push('6' + s + '9')
    res.push('8' + s + '8')
    res.push('9' + s + '6')
  }
  return res
}
console.log(helper(2)) // [ '11', '69', '88', '96' ]

Backtracking

Backtracking algorithm ideas

Backtracking is actually a heuristic algorithm. The biggest difference between this algorithm and brute-force search is that in the backtracking algorithm, it carefully explores forward step by step, and evaluates the situation detected at each step. If the current The situation has been unable to meet the requirements, then there is no need to continue, that is to say, it can help us avoid many detours.

The characteristic of the backtracking algorithm is that when an illegal situation occurs, the algorithm can go back to the previous scenario, which can be one step, sometimes even multiple steps, and then try other paths and methods. This also means that if you want to use the backtracking algorithm, you must ensure that there are multiple attempts every time.

Backtracking algorithm problem solving template

Problem-solving steps:

  • Determine whether the current situation is illegal, and return immediately if it is illegal;
  • Whether the current situation has met the recursive end condition, if so, save the current result and return;
  • Under the current situation, traverse all possible situations and try the next step;
  • After the recursion is complete, go back immediately, and the way to go back is to cancel the attempt made in the previous step.

Backtracking algorithm problem solving code

function fn(n) {
  // 第一步:判断输入或者状态是否非法?
  if (input/state is invalid) {
    return;
  }
  
  // 第二步:判读递归是否应当结束?
  if (match condition) {
    return some value;
  }

  // 遍历所有可能出现的情况
  for (all possible cases) {  
    // 第三步: 尝试下一步的可能性
    solution.push(case)
    // 递归
    result = fn(m)

    // 第四步:回溯到上一步
    solution.pop(case)
  }
}

Rounding up problem

LeetCode Question 39: Given an array candidates without repeating elements and a target number target , find all the combinations in candidates that can make the sum of numbers target The numbers in candidates can be repeatedly selected without limitation.

illustrate:
All numbers (including target ) are positive integers.
The solution set cannot contain repeated combinations.

Ideas to solve the problem of rounding up the whole number

The problem requires all non-repeated subsets, and the sum of the values of the elements in the subset is equal to a given goal.

Idea 1 : Violent law.

Luo lists all the subset combinations, and then judges whether their sum is the given target value one by one. The solution is very slow.

Idea 2 : Backtracking method.

Start with an empty set and add elements to it carefully.

Every time you add, check if the current sum is equal to the given target.

If the sum has exceeded the target, it means there is no need to try other elements, return and try other elements;

If the sum is equal to the target, the current combination is added to the result, indicating that we have found a combination that meets the requirements, and return at the same time, and try to find other sets.

Rounding up the problem code implementation

const combinationSum = function (candidates, target) {
  const results = []
  backtracking(candidates, target, 0, [], results)
  return results
}

const backtracking = function (candidates, target, start, solution, results) {
  if (target < 0) {
    return false
  }

  if (target === 0) {
    results.push([...solution])
    return true
  }

  for (let i = start; i < candidates.length; i++) {
    solution.push(candidates[i])
    backtracking(candidates, target - candidates[i], i, solution, results)
    solution.pop()
  }
}

console.log(combinationSum([1, 2, 3], 5))
// [ [ 1, 1, 1, 1, 1 ],
//   [ 1, 1, 1, 2 ],
//   [ 1, 1, 3 ],
//   [ 1, 2, 2 ],
//   [ 2, 3 ] ]

In the main function:

  • Define a results array to save the final result;
  • Call the function backtracking and pass in the initial situation and results . The initial situation here is to try from the first element, and the initial subset is empty.

In the backtracking function:

  • Check whether the current element sum has exceeded the value given by the target, and every time a new element is added, it is subtracted from the target sum;
  • If the sum has exceeded the target given value, return immediately to try other values;
  • If the sum is exactly equal to the target value, the current subset is added to the result.

In the circulation body:

  • Every time a new element is added, call backtracking recursively immediately to see if a suitable subset is found
  • After the recursion is complete, the most important thing is to delete the last tried element from the subset.

Above, the traceback is completed.

Reminder: This is one of the most classic retrospective questions. Although the sparrow is small, it has all kinds of internal organs. It fully embodies the various stages of the backtracking algorithm.

N queen problem

LeetCode question 51, N N×N chess board, one in each line and prevent them from attacking each other. Given an integer N , return the number of different solutions for the queen of N

N Queen’s Problem Solving Ideas

The key to solving the N is how to judge whether the placement of the current queens is legal.

nqueen

Use an array columns[] to record the column of the queen in each row. For example, if the queen is placed in the first row of 5 position on the column, then columns[0] = 6 . Place the queen from the first row, and only one in each row. Assuming that the previous placement will not cause conflicts, now place the queen on row row col , and check whether this placement is reasonable.

The method is to check whether there is a conflict in two directions.

N queen problem code implementation

First, from the first row to row , see if the queen placed in that row is on col , or on its diagonal, the code is as follows.

const check = function (row, col, columns) {
  for (let r = 0; r < row; r++) {
    // 其他皇后是否在当前放置皇后的列和对角线上
    if ((columns[r] = col || row - r == Math.abs(columns[r] - col))) {
      return false
    }
  }
  return true
}

Then perform the backtracking operation, the code is as follows.

const totalNQueens = function (n) {
  const results = []
  backtracking(n, 0, [], [], results)
  console.log('results: ', results) // results: [ [ [ 0, 0 ], [ 1, 0 ], [ 2, 0 ] ],[ [ 0, 2 ], [ 1, 0 ], [ 2, 0 ] ] ]
  console.log('count: ', results.length) // count: 2
}

const backtracking = function (n, row, columns, solution, results) {
  // 是否在所有 n 行里都摆好了皇后
  if (row === n) {
    results.push([...solution])
    return
  }

  // 尝试将皇后放置到当前行中的每一列
  for (let col = 0; col < n; col++) {
    columns[row] = col
    solution.push([row, col])

    // 检查是否合法,如果合法就继续到下一行
    if (check(row, col, columns)) {
      backtracking(n, row + 1, columns, solution, results)
    }
    solution.pop()
    // 如果不合法,就不要把皇后放在这列中
    columns[row] = -1
  }
}

totalNQueens(3)

wuwhs
6k 声望2.5k 粉丝

Code for work, write for progress!