Backtracking Algorithm - Combination

original title

https://leetcode.cn/problems/combinations/
insert image description here

train of thought

The backtracking algorithm is actually a kind of depth-first algorithm. That is to say, when we are using a depth-first algorithm (generally using recursion), sometimes we need to go back to the previous step after completing a step, which is called backtracking.

Well, since backtracking is a type of dfs, we have also talked about the steps of dfs before, and the most important step is an operation. First of all, the purpose of this question is to ask for a combination. Then assuming that k=2, does it mean that we select two times from the number and record all the results, then the so-called one operation is actually to select a number , and we know the combination [1, 2] and the combination [2, 1] is the same (considered as one), so when we choose [1, 2], when we choose [2] for the first time, we cannot choose [1] for the second time. So now that we already know what a single operation is, don't we just need to recurse. (Backtracking, etc. will be discussed later).
insert image description here
The analysis process is just like the picture above, because k is equal to 2, which means we need to select each small set twice. In the first operation, we can choose any number [1 2 3 4]. In the second operation, we need to remove the previously selected ones to prevent the situation of [1 2] and [2 1]; To remove the one selected last time (k=1), that is, to prevent the set from becoming [1 1].

Okay, the above content is the same as the depth optimization algorithm (recursion) I wrote earlier (in fact, the backtracking algorithm is generally implemented on the basis of recursion).

So why do we use backtracking?
Consider a question here. First of all, because we want to store subsets, right, we first define a large collection to store all subsets, and secondly define a subset to store a selection, such as [1, 2]. So let's think about the recursive process above. Here we must combine the above picture to realize the essence of backtracking.

  1. At the beginning our subset is empty. 【】
  2. Call the recursive function for the first time, select a 1, and the temporary subset at this time is [1]
  3. Then, call the recursive function again (that is, select the number for the second time), select a 2, and the set at this time is [1 2]
  4. Well, at this point we have selected twice (because k=2, so the selection has been completed), we will put the subset into the large set.
  5. The large set has a subset [1 2], then the function is executed at this time, go back to the last time, that is, {2 3 4} for you to choose.
  6. So if we don't backtrack at this time, is the temporary subset still [1 2]? The answer is yes .
  7. Many newcomers here will think, can I clear the temporary subset? In fact, it is completely unnecessary, because we have already selected the 1 in front of the temporary subset, and we roll back to the second layer. At this time, 1 is meaningful, so we only need to roll back 2 (that is, To roll back an operation, generally you have to roll back once when you recurse once).
  8. Then when we return [1 2] to [1], we can choose from { 2 3 4 }, since 2 has already been selected, that is to say, the next step is to choose 3, and the subset becomes [ 1 3].
  9. The above process is actually a depth-first algorithm (recursion), but we need a [rollback] every time we recurse, that is, [backtracking].
  10. The backtracking is also very down-to-earth in the code. It is nothing more than adding elements before recursion, and deleting the last element after recursion. The operation is not difficult, the main thing is to understand the thought.

the code

class Solution {
    
    
    List<List<Integer>> all = new ArrayList<>();
    List<Integer> part = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
    
    
        tCombine(n,k,0,new ArrayList());
        return all;
    }
    public void tCombine(int n,int k,int index,List<Integer> part) {
    
    
        if(k == 0){
    
    
            all.add(new ArrayList(part));
        }
        if(index == n){
    
    
            return;
        }
        for(int i = index;i < n;i++){
    
    
            part.add(i+1);
            tCombine(n,k-1,i+1,part);
            part.remove(part.size()-1);
        }
    }
}

insert image description here

Replenish

We said [backtracking] for this question, but this question can be further optimized, that is, reduce the number of recursions by [pruning]. I will talk about it later.

Guess you like

Origin blog.csdn.net/PaperJack/article/details/125337663