An article takes you through the backtracking algorithm

Backtracking algorithm applications

Backtracking algorithms and recursive algorithms generally appear at the same time, and the logic of backtracking is below the general recursive algorithms.
Generally speaking, recursive functions are actually backtracking functions. Backtracking generally does not appear alone.
Backtracking is actually a purely violent search algorithm. Some problems cannot be searched out with a for loop, and a backtracking algorithm must be used. Backtracking must be used for the following questions.

  • Combination problems. Find the set of k numbers among the N numbers according to certain rules. For example, given an array [1234], find a set of size 2 from it, and the result is 12,13,14,23,24,34
  • Cutting problem. There are several ways to cut a string according to certain rules. If a character string is given, how to cut and ensure that the substrings are all palindrome substrings.
  • Subset problem. How many eligible subsets are there in a set of N numbers? If you find a subset of the array [1234], the answer is 1,2,3,4,12,13,14,23,24,34,123,124,234,1234.
  • Arrangement problem. N numbers are arranged according to certain rules, and there are several arrangements. If one is 12, find its permutation, the answer is 12, 21. . The combination emphasizes what the elements are, not about the order, but about the arrangement.
  • Checkerboard problem. Such as Queen N, solve Sudoku.

Understanding of backtracking algorithms

The backtracking algorithm can be abstracted into a tree structure.
There is recursion in front of the backtracking algorithm, and recursion has termination conditions.
Backtracking is a by-product of recursion. As long as there is recursion, there will be backtracking.” Therefore, the backtracking method is often mixed with binary tree traversal and depth-first search, because both methods use recursion.

As shown in the figure, the width of the tree represents the size of the collection, which is generally traversed by a for loop, and the depth of the tree is the depth of recursion.

The template of the backtracking method is as follows

void backtracking(参数) {
    
    
    if (终止条件) {
    
    
        存放结果;#收集叶子节点
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
    
    
        处理节点;##如我们求数组【1234】的子集合,叶子节点有12,这里告诉12是如何来的。
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果##就是撤销处理节点 这一步。。。如求数组【1234】的组合,如开先放进了1,再放进了2,得到12为我们想要的组合。再把2回溯出去得到1,加入3,得到13组合
    }
}

Backtracking algorithm problem solving

1. Combination problem

Given two integers n and k, return all possible combinations of k numbers in 1...n.
Example:
Input: n = 4, k = 2
Output:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

Original solution: for loop, k is 2. two-layer for loop

n=4
results=[]
for i in range(1,n+1):
    for j  in range(i+1,n+1):
        result=str(i)+str(j)
        results.append(result)

print(results)

If n is 100 and k is 50, then 50 levels of for loops will start to suffocate.

Solving the Backtracking Algorithm As
we said above, "To solve the situation where n is 100 and k is 50, the brute force method needs to nest 50 levels of for loops, then the backtracking method uses recursion to solve the problem of nesting levels."
Recursion is used to do cascading nesting (it can be understood as opening k-level for loops), "Each recursion nests a for loop, then recursion can be used to solve the problem of multi-level nested loops."
At this time, everyone should know the number of recursive layers. For example, when n is 100 and k is 50, it is 50 recursive layers.
Some students were initially confused about recursion. In the backtracking method, recursion also has to nest for loops, which may directly faint!
If the brain hole simulates the process of backtracking search, it can definitely be suffocating, so abstract graphic structures are needed for further understanding.
"We are talking about the backtracking algorithm, you should know this! It is said that the problems solved by the backtracking method can be abstracted into a tree structure (N-ary tree), and it is much easier to understand backtracking with a tree structure."
Then I abstract the combination problem into the following tree structure:

It can be seen that the initial set of this tree is 1, 2, 3, 4. The number is taken from left to right, and the number taken is not repeated.
The first time we take 1, the set becomes 2, 3, 4, because k is 2, we only need to take another number, take 2, 3, 4 respectively, and get the set [1,2] [1,3 ] [1,4], and so on.
"Each time an element is selected from the set, the selectable range shrinks as the selection progresses, and the selectable range is adjusted."
"It can be found in the figure that n is equivalent to the width of the tree, and k is equivalent to the depth of the tree."

So how to traverse the tree and collect the result set we want?

"Every time a leaf node is searched in the graph, we found a result."

It is equivalent to only need to collect the results of reaching the leaf nodes, and then the combination set of k numbers of n numbers can be obtained.

Problem solving steps

The first step: Two global variables are defined here, one is used to store the single result path that meets the conditions, and the other is used to store the set result of the qualified results.

Step 2: There must be two parameters in the function. Since it is the number of k in the set n, then n and k are two int type parameters.
Then another parameter is needed, which is the int variable startIndex. This parameter is used to record where the collection starts to traverse in this layer of recursion (the collection is [1,...,n]).
Why is there such a startIndex?
"Each time you select an element from the set, the selectable range shrinks as the selection progresses. Adjusting the selectable range depends on startIndex."
From, after taking 1 in the set [1,2,3,4], the next level of recursion will take the number in [2,3,4], then how does the next level of recursion know from [2,3, 4] is the number that depends on startIndex.


If the size of the termination condition path array reaches k, it means that we have found a combination of subset size k.

class Solution:
    def combine(self, n: int, k: int):
        result = []
        def recall(n, k, startindex, result, path):
            if len(path) == k:
                result.append(path[:])
                return
            for i in range(startindex, n+1):
                path.append(i)
                recall(n, k, i+1, result, path)
                path.pop()
        recall(n, k, 1, result, [])
        return result

c=Solution()
d=c.combine(n=4,k=2)
print(d)

2. Segmentation problem

Given a string s, split s into some substrings so that each substring is a palindrome.
Return all possible splitting schemes for s.
Example:
Input: "aab"
Output:
[
["aa","b"],
["a","a","b"]
]

class Solution:
    def partition(self, s: str):
        #判断是否回文
        def helper(subStr):
            i, j = 0, len(subStr) - 1
            while i <= j:
                if subStr[i] != subStr[j]:
                    return False
                i += 1
                j -= 1
            return True

        def recall(s, size, start, subset):
            if start == size:#如果遍历完啦,结束
                res.append(subset[:])
                return
            for i in range(start, size):
                if not helper(s[start:i + 1]):
                    continue
                subset.append(s[start:i + 1])#中见间结果
                #print(subset)
                recall(s, size, i + 1, subset)
                subset.pop()

        res = []
        size = len(s)
        recall(s, size, 0, [])
        return res




if __name__ == "__main__":
    s = "aab"
    split_result = Solution().partition(s)
    print(split_result)

3. Subset problem

Given a set of integer arrays nums without repeated elements, return all possible subsets (power sets) of the array.
Note: The solution set cannot contain duplicate subsets.
Example:
Input: nums = [1,2,3]
Output:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1 ,2],
[]
]

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans = []
        
        # 存储符合要求的子集
        tmp = []

        n = len(nums)
        def helper(idx):
            # 先添加子集
            ans.append(tmp[:])

            for i in range(idx, n):
                tmp.append(nums[i])
                # 避免重复,每次递归,从下一个索引开始
                helper(i+1)
                # 回溯
                tmp.pop()


        helper(0,0)
        return ans

4. Full arrangement problem

Given a sequence without repeated numbers, return all possible permutations.
Example:
Input: [1,2,3]
Output:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1 ,2],
[3,2,1]
]

class Solution:
    def permute(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        def backtrack(first = 0):
            # 所有数都填完了
            if first == n:  
                res.append(nums[:])
            for i in range(first, n):
                # 动态维护数组
                nums[first], nums[i] = nums[i], nums[first]
                # 继续递归填下一个数
                backtrack(first + 1)
                # 撤销操作
                nums[first], nums[i] = nums[i], nums[first]
        
        n = len(nums)
        res = []
        backtrack()
        return res


Insert picture description here
Insert picture description here

Guess you like

Origin blog.csdn.net/kobeyu652453/article/details/111150094