Take the questions like this, and kill the leetcode questions

Keywords: Java MySQL

Today, let's talk about the problem-solving methods of dfs. These methods are all the experiences after summing up, which are worth learning.

Looking at dfs from binary tree

In fact, the idea of binary tree is very simple. When we just started to learn binary tree, the most common way to do binary tree traversal is recursive traversal. In fact, you will find that the problem-solving methods of binary tree problems are basically recursive solutions. We only need to take one step, and the others are recursive solutions.

Let's take a look at the recursive versions of the traversal of the binary tree.

//Preorder ergodic
void traverse(TreeNode root) {
    System.out.println(root.val);
    traverse(root.left);
    traverse(root.right);
}

//Middle order ergodic
void traverse(TreeNode root) {
    traverse(root.left);
    System.out.println(root.val);
    traverse(root.right);
}

//Subsequent traversal
void traverse(TreeNode root) {
    traverse(root.left);
    traverse(root.right);
    System.out.println(root.val);
}

In fact, you will find that the whole frame of binary tree traversal can be seen in the process of binary tree traversal. In fact, this is also the whole frame of binary tree problem-solving.

void traverse(TreeNode root) {
    //Here, the output will be changed into other operations. We only complete the first step, and the next step is done by recursion.
    traverse(root.left);
    traverse(root.right);
}

When we solve the problem, we only need to think about how to implement the current operation. The latter is implemented by recursion. As for whether to use the pre order middle order or the post order traversal, it is implemented by the specific situation.

Here are a few binary tree warm-up questions to experience this solution.

In addition, I've written original articles and explained them systematically. You can have a look at them, and you'll get something.

Serial number Original boutique
1 [original] distributed architecture series
2 [original] practical Activiti workflow tutorial
3 [original] in depth understanding of Java virtual machine tutorial
4 [original] Java8 latest tutorial
5 [original] art world of MySQL

1. How to add values in all nodes of the spanning tree

First of all, it's the same. Let's write the framework first.

void traverse(TreeNode root) {
    //Here, the output will be changed into other operations. We only complete the first step, and the next step is done by recursion.
    traverse(root.left);
    traverse(root.right);
}

Next, consider what needs to be done in the current step. Here, of course, add one to the current node.

void traverse(TreeNode root) {
    if(root == null) {
        return;
    }
    //Add one to the current node instead.
    root.val += 1;
    traverse(root.left);
    traverse(root.right);
}

Does discovery come naturally?

Upset? Another simple one.

2. How to judge whether two binary trees are the same

In this question, we directly consider what needs to be done in the current step, that is, what kind of situation is it? This is the same binary tree?

1) The current node of the two trees is equal to null: root1 = = null & & root2 = = null, and return true at this time.
2) Either of the current nodes of the two trees is empty: root1 = = null | root2 = = null, which is of course false at this time.
3) The current node of both trees is not empty, but Val is different: root1. Val! = root2.val, return false.

So the answer is obvious.

boolean isSameTree(TreeNode root1, TreeNode root2) {
    // If it's all empty
    if (root1 == null && root2 == null) return true;
    // Empty, empty
    if (root1 == null || root2 == null) return false;
    // Both are empty, but val is not
    if (root1.val != root2.val) return false;
    // Do it recursively
    return isSameTree(root1.left, root2.left) && isSameTree(root1.right, root2.right);
}

With the above explanation, I believe you have a basic idea. Now let's have some difficult questions. Let's have a quiz.

3. Analysis of leetcode medium difficulty

114. Expanding binary tree into linked list

This problem is of medium difficulty in binary tree, but the passing rate is very low. Let's use the above idea to see if we can solve this problem easily.

At first glance, according to the previous ideas, you can choose to solve this problem in the way of preorder traversal first, which is OK, but it's more troublesome, because the way of preorder traversal will change the direction of the right node, which leads to more trouble. Then, if the preorder traversal doesn't work, you should consider the middle order and post order traversal. Because, when you expand, you only need to change the left The direction of the right node, so the best way here is to use follow-up traversal. Since it's follow-up traversal, we can quickly write out the framework of follow-up traversal.

public void flatten(TreeNode root) {
    if(root == null){
        return;
    }
    flatten(root.left);
    flatten(root.right);
    
    //Consider what to do now
}

In this way, the basic idea of this topic will come out. Then, we only need to consider what needs to be done in the current step to solve this problem.

Current step: since it is a post order traversal, the order is left and right. From the order of expansion, we can see that it is obvious that the left node is connected first, and then the right node. Therefore, we must first save the value of the right node, and then the left node. At the same time, after our expansion, only the right node, so the left node should be set to null.

After analysis, the code can be written directly.

public void flatten(TreeNode root) {
    if(root == null){
        return;
    }
    flatten(root.left);
    flatten(root.right);
    
    //Consider what to do now
    TreeNode temp = root.right;//
    root.right = root.left;//Right pointer to left node
    root.left = null;//Left node value is empty
    while(root.right != null){
        root = root.right;
    }
    root.right = temp;//Finally, connect the right node to the right pointer
}

In the end, this is the answer. This is not the best answer. However, it may be the best way to understand the problem of binary tree. At the same time, it is very helpful for you to understand the idea of dfs algorithm.

105. Constructing binary trees from traversal sequences of preorder and middle order

This question is also a good one. In fact, when we study data structure, this question often appears in the way of solving and answering questions. Let's do it in the exam. It's really impressive. Here, let's see how to solve it with code.

It's the same routine, the same way of thinking, and the same taste. Let's stir fry this dish again.

First of all, determine whether to traverse in the first order, the middle order or the last order. Since the binary tree is derived from the former order and the middle order, the former order traversal is better.

Here we think about what we should do in the current step, and then make this dish directly.

Current step: recall the idea of doing this topic before you will find that when we construct a binary tree, the idea is this: the first element of preorder traversal must be the root node a, then, the element a of current preorder traversal, in the middle order traversal, on the left side of the element a is the element of the left subtree, and on the right side of the element a is the element of the left subtree, In this way, whether we have considered the current step clearly, then the only thing we have to do is to find the location of the element a in the middle order traversal array, and other recursions can be solved.

Don't talk much, look at the code.

public TreeNode buildTree(int[] preorder, int[] inorder) {
    //The first element of the current preamble traversal
    int rootVal = preorder[0];
    root = new TreeNode();
    root.val = rootVal;

    //Get the position in the order traversal array in inorder
    int index = 0;
    for(int i = 0; i < inorder.length; i++){
        if(rootVal == inorder[i]){
            index = i;
        }
    }

    //Do it recursively
}

This step is done, and the next step is what recursion needs to do. Let the computer work.

public TreeNode buildTree(int[] preorder, int[] inorder) {
    //The first element of the current preamble traversal
    int rootVal = preorder[0];
    root = new TreeNode();
    root.val = rootVal;

    //Get the position in the order traversal array in inorder
    int index = 0;
    for(int i = 0; i < inorder.length; i++){
        if(rootVal == inorder[i]){
            index = i;
        }
    }

    //Do it recursively
    root.left = buildTree(Arrays.copyOfRange(preorder,1,index+1),Arrays.copyOfRange(inorder,0,index));
    root.right = buildTree(Arrays.copyOfRange(preorder,index+1,preorder.length),Arrays.copyOfRange(inorder,index+1,inorder.length));
    return root;
}

Finally, we need to deal with the boundary conditions to prevent root from being null.

TreeNode root = null;

if(preorder.length == 0){
    return root;
}

ok, this dish is fried according to the template. Believe you, you will also copy the later dishes.

2. Looking at dfs from the island problem of leetcode

1. Step by step

This kind of problem is still very many in leetcode, and you will often encounter this kind of problem in the written test, so it is very important to find a solution. In fact, in the end, you will find that this kind of problem, you will no longer find it difficult after meeting.

Let's take a look at the topic first.

The meaning of the topic is very simple. There is a two-dimensional array, in which the numbers are 0 and 1, 0 represents the water area, 1 represents the land, let you calculate the number of land, that is, the number of islands.

So how to solve these problems?

In fact, we can see this problem from the aforementioned problem of looking at dfs from binary tree. The feature of binary tree is very obvious, that is, only two branches can be selected.

So, you have the following traversal template.

//Preorder ergodic
void traverse(TreeNode root) {
    System.out.println(root.val);
    traverse(root.left);
    traverse(root.right);
}

However, when returning to this topic, you will find that our entire data structure is a two-dimensional diagram, as shown below.

How do you traverse this graph? Is it like this?

In the position of (i, j), whether there are four directions can be traversed, then whether there is a new way to solve the problem.

So we can write the dfs template code.

void dfs(int[][] grid, int i, int j) {
    // Access four adjacent directions: up, down, left and right
    dfs(grid, i - 1, j);
    dfs(grid, i + 1, j);
    dfs(grid, i, j - 1);
    dfs(grid, i, j + 1);
}

You will find out whether it is similar to the traversal of a binary tree, but there are two more directions.

Finally, there is another problem to consider: base case. In fact, the binary tree also needs to discuss base case. However, it is very simple. When root == null, it is base case.

In fact, the base case here is not difficult, because this two-dimensional graph has a boundary. When dfs finds that it exceeds the boundary, it needs to be judged. Therefore, we add the boundary conditions.

void dfs(int[][] grid, int i, int j) {
    // Judge base case
    if (!inArea(grid, i, j)) {
        return;
    }
    // If this grid is not an island, go straight back
    if (grid[i][j] != 1) {
        return;
    }
    
    // Access four adjacent directions: up, down, left and right
    dfs(grid, i - 1, j);
    dfs(grid, i + 1, j);
    dfs(grid, i, j - 1);
    dfs(grid, i, j + 1);
}

// Judge whether coordinate (r, c) is in Grid
boolean inArea(int[][] grid, int i, int j) {
    return 0 <= i && i < grid.length 
            && 0 <= j && j < grid[0].length;
}

By now, the topic is almost finished. However, we need to pay attention to the fact that when we visit a node, we need to mark it. We can use bool or other numbers to mark it, or we may have circular recursion.

So, the final solution comes out.

void dfs(int[][] grid, int i, int j) {
    // Judge base case
    if (!inArea(grid, i, j)) {
        return;
    }
    // If this grid is not an island, go straight back
    if (grid[i][j] != 1) {
        return;
    }

    //Mark traversal with 2
    grid[i][j] = 2; 
    
    // Access four adjacent directions: up, down, left and right
    dfs(grid, i - 1, j);
    dfs(grid, i + 1, j);
    dfs(grid, i, j - 1);
    dfs(grid, i, j + 1);
}

// Judge whether coordinate (r, c) is in Grid
boolean inArea(int[][] grid, int i, int j) {
    return 0 <= i && i < grid.length 
            && 0 <= j && j < grid[0].length;
}

Not good enough? One more question.

2. Another round

This question is similar to the one above, but here is to find the area of the largest island. Since the area of each cell is 1, the final area is the number of cells.

The solution to this problem is basically the same as the one above. We can copy the above code and change it.

class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        if(grid == null){
            return 0;
        }
        int max = 0;
        for(int i = 0; i < grid.length; i++){
            for(int j = 0; j < grid[0].length; j++){
                if(grid[i][j] == 1){
                    max = Math.max(dfs(grid, i, j), max);
                }
            }
        }
        return max;
    }

    int dfs(int[][] grid, int i, int j) {
        // Judge base case
        if (!inArea(grid, i, j)) {
            return 0;
        }
        // If this grid is not an island, go straight back
        if (grid[i][j] != 1) {
            return 0;
        }

        //Mark traversal with 2
        grid[i][j] = 2; 
        
        // Access four adjacent directions: up, down, left and right
        return 1 + dfs(grid, i - 1, j) + dfs(grid, i + 1, j) + dfs(grid, i, j - 1) + dfs(grid, i, j + 1);
    }

    // Judge whether coordinate (r, c) is in Grid
    boolean inArea(int[][] grid, int i, int j) {
        return 0 <= i && i < grid.length 
                && 0 <= j && j < grid[0].length;
    }
}

Basic idea: each time dfs is performed, the number of islands is + 1, and then the maximum value of all islands is calculated.

Let's see how efficient our code is.

It seems that it's not bad, right, that's how it works!!!

Finally, this article has been written for nearly a week. I don't know how it is written. However, I try my best to express what I think clearly. It's mainly a way of thinking and solving problems. There must be many other ways. Go to LeetCode to see it.

Well, I've been writing for a long time. Let's look at other articles in the next article. I hope they can help you. See you again!!

Posted by guymclaren on Sun, 21 Jun 2020 19:17:41 -0700