The topic of backtracking algorithm, do this, spike! !

Give it a like, take a look, good habit! This article GitHub https://github.com/OUYANGSIHAI/JavaInterview has been included. This is the Java interview summary of the first-line big factory that I have spent 3 months summarizing. I have taken the offer from the big factory. In addition, the original article was first published on my personal blog: blog.ouyangsihai.cn , welcome to visit.

This article will explain how to do the leetcode backtracking algorithm topic. During this period of time, I have reviewed all the backtracking algorithm topics on leetcode and found some of the rules. Therefore, I want to write an article to summarize, Afraid of forgetting later.

After brushing the topic of backtracking algorithm, I found that it can be summarized into three categories: subset problem, combination problem, and permutation problem. What do these three categories mean? Let me give an example to illustrate each.

Subset problems , for example, arrays [1,2,3], then the corresponding subset problem is that the subsets of this array are: [],[1],[2],[3],[1,3],[2,3],[1,2],[1,2,3], this is the subset of this array, there are many problems of this type on leetcode, and some elements in the array of questions It can be repeated, and then come to the subset problem.

Combination problems , for example, arrays [1,2,3], combine the possible choices with target of 3, then there are: [1,2],[3], this is the combination problem in leetcode.

The permutation problem is relatively simple. For example, our common full permutation problem, leetcode also has this type of problem.

In this article, we will talk about how to use the backtracking algorithm to solve these problems.

1 Step by step to explain the backtracking algorithm framework

At the beginning, I still want to use a simple example to show you step by step how the problem of backtracking algorithm should be solved step by step. In the end, through this problem, we can roughly sort out the solution of a backtracking algorithm. Framework; first look at the following topic, which is a subset of the topic, and the difficulty of the topic is medium.

This topic, the framework given by the topic is like this.

    public List<List<Integer>> subsets(int[] nums) {
            
    }

So, we know that we first construct a List<List<Integer>>type of return value.

    List<List<Integer>> list = new ArrayList<>();

Next, we start to write the backtracking method.

    public void backTrace(int start, int[] nums, List<Integer> temp){
        for(int j = 0; j < nums.length; j++){
            temp.add(nums[j]);
            backTrace(j+1,nums,temp);
            temp.remove(temp.size()-1);
        }
    }

At first, it may be written like the above, passing in an array numsand saving the result. Then, each time the array nums is traversed, the current element is added, startand temp集合when the recursion comes back, the element that has just been added will be backtracked, and the element just added will be deleted. Is it just retrospective thinking?

After writing the basic framework in this way, there is another question to think about is the base case , so what is the base case for this topic? In fact, because it is a subset, each step needs to be added to the result set temp, so there are no restrictions.

    public void backTrace(int start, int[] nums, List<Integer> temp){
        //每次都保存结果
        list.add(new ArrayList<>(temp));
        for(int j = 0; j < nums.length; j++){
            temp.add(nums[j]);
            backTrace(j+1,nums,temp);
            temp.remove(temp.size()-1);
        }
    }

Finally, let's complete it again, and the complete code is out.

    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        if(nums.length == 0){
            return null;
        }
        List<Integer> temp = new ArrayList<>();
        backTrace(0, nums, temp);
        return list;
    }

    public void backTrace(int start, int[] nums, List<Integer> temp){
        list.add(new ArrayList<>(temp));
        for(int j = 0; j < nums.length; j++){
            temp.add(nums[j]);
            backTrace(j+1,nums,temp);
            temp.remove(temp.size()-1);
        }
    }

ok, let's run it and see how it goes.

In addition, for this knowledge, I have written original articles and explained it in a more systematic way. You can take a look at it, and there will be some gains.

serial number Original boutique
1 [Original] Distributed Architecture Series Articles
2 [Original] Actual Activiti Workflow Tutorial
3 [Original] In-depth understanding of Java virtual machine tutorial
4 [Original] Java8 latest tutorial
5 [Original] The Art World of MySQL

He said that I exceeded the time limit, indicating that there is a problem with the algorithm. Let's look at the code we wrote above. We found that, in fact, every time we traverse the array, we traverse from 0, resulting in many repeated elements traversed. , that is, our startvariables are not used. Finally, we do not start from 0 each time when traversing, but start traversing from the current start. We exclude the selected elements and take a look at the results.

    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        if(nums.length == 0){
            return null;
        }
        List<Integer> temp = new ArrayList<>();
        backTrace(0, nums, temp);
        return list;
    }

    public void backTrace(int start, int[] nums, List<Integer> temp){
        list.add(new ArrayList<>(temp));
        //从start开始遍历,避免重复
        for(int j = start; j < nums.length; j++){
            temp.add(nums[j]);
            backTrace(j+1,nums,temp);
            temp.remove(temp.size()-1);
        }
    }

Found a perfect pass, good job! !

In addition, we should pay attention to one point: list.add(new ArrayList<>(temp));Do not write list.add(temp);it, otherwise, the output result will be an empty set, and you should know why after thinking about it.

Pass, this topic, in fact, we can sort out a general framework of the backtracking algorithm, and then do other topics in the future, follow the cat and draw the tiger, and just do one operation.

Going back to the backTrace function, it is actually a selection/removal selection process. The for loop is also a selection process. Another point is that the base case needs to be processed in this function. Then, we can sort out the frame.

    public void backTrace(int start, int[] nums, List<Integer> temp){
        base case处理
        //选择过程
        for(循环选择){
            选择
            backTrace(递归);
            撤销选择
        }
    }

ok, I have already talked about a subset problem. Next, I will come up with a more interesting subset topic.

2 Subset problems

The topic used to introduce the backtracking algorithm framework is actually relatively simple, but the idea is unchanged, this framework is very important, and other topics are basically modified on the above framework, such as pruning operations.

90. Subset II Moderate Difficulty

Compared with the previous subset questions, the difference is that the complement contains repeated subsets, that is, the order cannot be changed, and the subsets with the same elements appear.

The framework of this topic is still the same, but we need to do a simple pruning operation: how to exclude duplicate subsets .

There are two ways to solve this problem, and when the other problems later have the restriction that repeated subsets cannot appear , they can be solved by these two methods.

  • Method 1: Use the Set deduplication feature to solve the problem

We still remove the above framework first, and then modify it.

    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        if(nums.length == 0){
            return null;
        }
        List<Integer> temp = new ArrayList<>();
        backTrace(0, nums, temp);
        return list;
    }

    public void backTrace(int start, int[] nums, List<Integer> temp){
        list.add(new ArrayList<>(temp));
        //从start开始遍历,避免重复
        for(int j = start; j < nums.length; j++){
            temp.add(nums[j]);
            backTrace(j+1,nums,temp);
            temp.remove(temp.size()-1);
        }
    }

Because we want to use the characteristics of Set to deduplicate, we need to add this variable Set<List<Integer>> set = new HashSet<>();. In addition, in order to ensure the order, we will sort again Arrays.sort(nums), which can avoid the problem of repeated subsets with the same elements but different orders.

So, the result is out.

    List<List<Integer>> list = new ArrayList<>();
    Set<List<Integer>> set = new HashSet<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        if(nums.length == 0){
            return null;
        }
        //排序
        Arrays.sort(nums);
        List<Integer> temp = new ArrayList<>();
        backTrace(0, nums, temp);
        return list;
    }

    public void backTrace(int start, int[] nums, List<Integer> temp){
        //set去重操作
        if(!set.contains(temp)){
            set.add(new ArrayList<>(temp));
            list.add(new ArrayList<>(temp));
        }
        
        for(int j = start; j < nums.length; j++){
            temp.add(nums[j]);
            backTrace(j+1,nums,temp);
            temp.remove(temp.size()-1);
        }
    }

Take a look at the results and find that the efficiency is not very good.

Then let's look at another pruning strategy for deduplication.

  • Method Two:i > start && nums[i-1] == nums[i]

Why is this pruning strategy possible? Don't worry, let me draw a picture to explain it.

So, we can do it this way.

    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        if(nums.length == 0){
            return null;
        }
        Arrays.sort(nums);
        List<Integer> temp = new ArrayList<>();
        backTrace(0, nums, temp);
        return list;
    }

    public void backTrace(int start, int[] nums, List<Integer> temp){
        list.add(new ArrayList<>(temp));
        
        for(int i = start; i < nums.length; i++){
            //剪枝策略
            if(i > start && nums[i] == nums[i-1]){
                continue;
            }
            temp.add(nums[i]);
            backTrace(i+1,nums,temp);
            temp.remove(temp.size()-1);
        }
    }

Whoops, it seems to be okay.

3 Combination problems

After solving the previous subset problem, you will find that the latter combination problem and arrangement problem are not a big problem, they are basically routines.

39. The combined total is moderately difficult

This topic is not much different from the previous one, just need to pay attention to one point: each number can be selected repeatedly without limit , what we need to do is when recursion, ithe subscript is not from the i+1beginning, but from the ibeginning .

    backTrace(i,candidates,target-candidates[i], temp);

Let's look at the complete code.

    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        if(candidates.length == 0 || target < 0){
            return list;
        }
        List<Integer> temp = new ArrayList<>();
        backTrace(0,candidates,target,temp);
        return list;
    }

    public void backTrace(int start, int[] candidates, int target, List<Integer> temp){
        //递归的终止条件
        if (target < 0) {
            return;
        }

        if(target == 0){
            list.add(new ArrayList<>(temp));
        } 

        for(int i = start; i < candidates.length; i++){
            temp.add(candidates[i]);
            backTrace(i,candidates,target-candidates[i], temp);
            temp.remove(temp.size()-1);
        }
    }

It's that simple! ! !

So, another combinatorial question.

40. Combined Sum II Moderate Difficulty

When you look at the title, you will find that it is almost the same. Indeed, each number can only be used once, and it cannot contain repeated combinations. Therefore, use the above deduplication method to solve it. Without further ado, let's get to the code.

    List<List<Integer>> lists = new LinkedList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        if(candidates.length == 0 || target < 0){
            return lists;
        }
        Arrays.sort(candidates);
        List<Integer> list = new LinkedList<>();
        backTrace(candidates,target,list, 0);

        return lists;
    }

    public void backTrace(int[] candidates, int target, List<Integer> list, int start){
        if(target == 0){
            lists.add(new ArrayList(list));
        }
        
        for(int i = start; i < candidates.length; i++){
            if(target < 0){
                break;
            }
            //剪枝:保证同一层中只有1个相同的元素,不同层可以有重复元素
            if(i > start && candidates[i] == candidates[i-1]){
                continue;
            }
            list.add(candidates[i]);
            backTrace(candidates,target-candidates[i],list,i+1);
            list.remove(list.size()-1);
        }
    }

Also the perfect solution! !

4 Full permutation problem

Let's start with the most basic full permutation problem and solve it quickly.

46. ​​Medium difficulty

This is the full arrangement, but the order of the elements is different. Therefore, the pruning we need to do is to exclude some in the temp collection.

code above.

    List<List<Integer>> lists = new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {
        if(nums.length == 0){
            return lists;
        }
        List<Integer> list = new ArrayList<>();

        backTrace(nums,list,0);

        return lists;
    }

    public void backTrace(int[] nums, List<Integer> temp, int start){
        if(temp.size() == nums.length){
            lists.add(new ArrayList(temp));
            return;
        }

        for(int i = 0; i < nums.length; i++){
            //排除已有元素
            if(temp.contains(nums[i])){
                continue;
            }
            temp.add(nums[i]);
            backTrace(nums,temp,i+1);
            temp.remove(temp.size() - 1);
        }
    }

Isn't it boring, arrange it! !

47. Full Rank II Moderate Difficulty

Although this question is also a full arrangement, it is a bit more difficult than the previous one. There are two qualifications: there are repeating elements, but they cannot contain repeating arrangements .

We know how to solve the non-repeated full arrangement, just use the previous deduplication method, but how to ensure that the sets with the same elements do not have repeated arrangements?

Here we need to add a visited array to record whether the current element has been visited , so that the problem can be solved.

  public List<List<Integer>> result = new ArrayList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        if(nums.length == 0){
            return result;
        }
        Arrays.sort(nums);
        findUnique(nums,new boolean[nums.length],new LinkedList<Integer>());
        return result;
    }
    public void findUnique(int[] nums, boolean[] visited,List<Integer> temp){
        //结束条件
        if(temp.size() == nums.length){
            result.add(new ArrayList<>(temp));
            return ;
        }
        //选择列表
        for(int i = 0; i<nums.length; i++){
            //已经选择过的不需要再放进去了
            if(visited[i]) continue;
            //去重
            if(i>0 && nums[i] == nums[i-1] && visited[i-1]) break;
            
            temp.add(nums[i]);
            visited[i] = true;

            findUnique(nums,visited,temp);

            temp.remove(temp.size()-1);
            visited[i] = false;
        }
    }

This solves the problem.

5 is not a summary

So far, the subset, combination, and full permutation problems have been solved. From the step-by-step explanation of the framework to the analysis of specific problems, everything is covered.

This article has been written for two days, and it's almost here. It's not easy to be original, so give it a like!

Finally, I will share the Java interview + Java back-end technology study guide that I have summarized in three months . This is my summary of the past few years and the spring recruitment. I have already got an offer from a big company, and organized it into an e-book. No thanks, the directory is as follows:

Now share it with everyone for free, and you can get it by replying to the interview in the technical circle of programmers in my public account below.

{{o.name}}
{{m.name}}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324079242&siteId=291194637