The dfs problem is answered in this way, and the leetcode problem is killed in seconds

Today, let's talk about the problem-solving methods of dfs. These methods are all the experience after summarizing, and there are places that are worth learning from.

1 Looking at dfs from a binary tree

The idea of ​​binary tree is actually very simple. When we just started to learn binary tree, is the most common method when doing binary tree traversal is recursive traversal? In fact, you will find that the problem-solving method of binary tree is basically recursive. To solve the problem, we only need to take one step, and the rest is done by recursion.

Let's first look at the recursive versions of preorder, inorder, and postorder traversal of a binary tree.

//前序遍历
void traverse(TreeNode root) {
    System.out.println(root.val);
    traverse(root.left);
    traverse(root.right);
}

//中序遍历
void traverse(TreeNode root) {
    traverse(root.left);
    System.out.println(root.val);
    traverse(root.right);
}

//后续遍历
void traverse(TreeNode root) {
    traverse(root.left);
    traverse(root.right);
    System.out.println(root.val);
}

In fact, you will find that the process of binary tree traversal can see an overall framework of binary tree traversal. In fact, this is also the overall framework of binary tree problem solving as follows.

void traverse(TreeNode root) {
    //这里将输出变成其他操作,我们只完成第一步,后面的由递归来完成。
    traverse(root.left);
    traverse(root.right);
}

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

Let's take a few binary tree warm-up questions to experience this problem-solving method.

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

1. How to add one to the value of all nodes in the binary tree

The first is still the same, let's write the framework first.

void traverse(TreeNode root) {
    //这里将输出变成其他操作,我们只完成第一步,后面的由递归来完成。
    traverse(root.left);
    traverse(root.right);
}

Next, consider what the current step needs to do, here, of course, add one to the current node.

void traverse(TreeNode root) {
    if(root == null) {
        return;
    }
    //这里改为给当前的节点加一。
    root.val += 1;
    traverse(root.left);
    traverse(root.right);
}

Did the discovery come naturally?

accurate? Another simple one.

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

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

1) The current nodes of the two trees are equal to null: root1 == null && root2 == null, at this time return true. 2) Any one of the current nodes of the two trees is empty: root1 == null || root2 == null, of course it is false at this time. 3) The current nodes of the two trees are not empty, but the vals are different: root1.val != root2.val, return false.

So, the answer is obvious.

boolean isSameTree(TreeNode root1, TreeNode root2) {
    // 都为空的话
    if (root1 == null && root2 == null) return true;
    // ⼀个为空,⼀个⾮空
    if (root1 == null || root2 == null) return false;
    // 两个都⾮空,但 val 不⼀样
    if (root1.val != root2.val) return false;
    // 递归去做
    return isSameTree(root1.left, root2.left) && isSameTree(root1.right, root2.right);
}

With the above explanation, I believe that you already have a basic idea. Let's take some difficult questions and try them out.

3. leetcode medium difficulty parsing

114. Expand binary tree to linked list

This problem is a medium difficulty problem in the binary tree, but the pass rate is very low, so let's use the above ideas to see if this problem can be easily solved.

At first glance, according to the previous ideas, you may first choose the way of preorder traversal to solve this problem, which is possible, but it is more troublesome, because the way of preorder traversal will change the direction of the right node, which will cause more trouble. , then, if pre-order traversal does not work, consider in-order and post-order traversal. Since, when expanding, you only need to change the directions of the left and right nodes, so the best way here is to use subsequent traversal, since it is Subsequent traversal, then we can quickly write the framework of subsequent traversal.

public void flatten(TreeNode root) {
    if(root == null){
        return;
    }
    flatten(root.left);
    flatten(root.right);
    
    //考虑当前一步做什么
}

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

The current step: Since it is a post-order traversal, the order is 左右中. From the order of expansion, we can see that it is obvious that the left node is connected first, and then the right node is connected. Therefore, we must save the value of the right node first, and then connect the left node. , and at the same time, after our expansion, there is 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);
    
    //考虑当前一步做什么
    TreeNode temp = root.right;//
    root.right = root.left;//右指针指向左节点
    root.left = null;//左节点值为空
    while(root.right != null){
        root = root.right;
    }
    root.right = temp;//最后再将右节点连在右指针后面
}

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

105. Construct binary tree from preorder and inorder traversal sequence

This topic is also a very good topic, and in fact, when we study data structure, this topic often appears in the form of answering questions, let us do it during the exam, it is really impressive, here, let's see how to solve it with code .

Still the same routine, the same idea, and the same taste, let’s fry this dish again.

First, determine pre-order traversal, in-order traversal or post-order traversal. Since the binary tree is derived from pre-order traversal and in-order traversal, pre-order traversal is better.

Here we directly consider what should be done in the current step, and then directly make this dish.

The current step : Recalling the idea of ​​​​doing this topic before, you will find that when we construct the binary tree, the idea is like this, the first element of the preorder traversal must be the root node a, then the element a of the current preorder traversal, In in-order traversal, the element to the left of the element a is the element of the left subtree, and the element to the right of the element a is the element of the left subtree, so whether the current step is considered clearly, then the only thing we have to do is Find the position of the element a in the in-order traversal array, and the other recursion can be solved.

Without further ado, look at the code.

public TreeNode buildTree(int[] preorder, int[] inorder) {
    //当前前序遍历的第一个元素
    int rootVal = preorder[0];
    root = new TreeNode();
    root.val = rootVal;

    //获取在inorder中序遍历数组中的位置
    int index = 0;
    for(int i = 0; i < inorder.length; i++){
        if(rootVal == inorder[i]){
            index = i;
        }
    }

    //递归去做
}

This step is done, and then there is the recursion to do, let the computer do the work.

public TreeNode buildTree(int[] preorder, int[] inorder) {
    //当前前序遍历的第一个元素
    int rootVal = preorder[0];
    root = new TreeNode();
    root.val = rootVal;

    //获取在inorder中序遍历数组中的位置
    int index = 0;
    for(int i = 0; i < inorder.length; i++){
        if(rootVal == inorder[i]){
            index = i;
        }
    }

    //递归去做
    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, deal with the boundary conditions to prevent the situation where the root is null.

TreeNode root = null;

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

ok, this dish is just fried according to the template. I believe you, you will also copy and fry the following dishes.

2 Looking at dfs from leetcode's island problem

1. Step by step

There are still many questions of this type in leetcode, and you will often encounter such questions in the written test. Therefore, it is very important to find a solution. In fact, in the end, you will find that after you master this type of questions, No more difficult questions.

Let's look at the topic first.

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

So how to solve such problems?

In fact, we can look at this problem from the problem of looking at dfs from a binary tree as mentioned earlier. The characteristic of a binary tree is obvious, that is, there are only two branches to choose from.

So, there is the following traversal template.

//前序遍历
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 graph, as shown below.

When you traverse this graph, how do you traverse it? Is it like this?

At the position of (i, j), can there be four directions that can be traversed, then is there a new problem-solving idea for this problem?

In this way, we can write the dfs template code for this.

void dfs(int[][] grid, int i, int j) {
    // 访问上、下、左、右四个相邻方向
    dfs(grid, i - 1, j);
    dfs(grid, i + 1, j);
    dfs(grid, i, j - 1);
    dfs(grid, i, j + 1);
}

You will find that it is very similar to the traversal of a binary tree, except that there are two more directions.

Finally, there is another issue that needs to be considered: base case In fact, the binary tree also needs to discuss the base case, but, very simple, when root == nullthe time is, it is the base case.

The base case here is actually not difficult, because this two-dimensional graph has boundaries. When dfs is found to be beyond the boundary, it needs to be judged. Therefore, we add boundary conditions.

void dfs(int[][] grid, int i, int j) {
    // 判断 base case
    if (!inArea(grid, i, j)) {
        return;
    }
    // 如果这个格子不是岛屿,直接返回
    if (grid[i][j] != 1) {
        return;
    }
    
    // 访问上、下、左、右四个相邻方向
    dfs(grid, i - 1, j);
    dfs(grid, i + 1, j);
    dfs(grid, i, j - 1);
    dfs(grid, i, j + 1);
}

// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int i, int j) {
    return 0 <= i && i < grid.length 
        	&& 0 <= j && j < grid[0].length;
}

At this point, this topic has almost been completed, but there is one more thing we need to pay attention to. When we visit a node, we need to mark it. You can use bool or other numbers to mark, otherwise there may be a loop. recursive case.

So, the final solution is out.

void dfs(int[][] grid, int i, int j) {
    // 判断 base case
    if (!inArea(grid, i, j)) {
        return;
    }
    // 如果这个格子不是岛屿,直接返回
    if (grid[i][j] != 1) {
        return;
    }

    //用2来标记已经遍历过
    grid[i][j] = 2; 
    
    // 访问上、下、左、右四个相邻方向
    dfs(grid, i - 1, j);
    dfs(grid, i + 1, j);
    dfs(grid, i, j - 1);
    dfs(grid, i, j + 1);
}

// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int i, int j) {
    return 0 <= i && i < grid.length 
        	&& 0 <= j && j < grid[0].length;
}

Not cool enough? One more question.

2. One more shot

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

The method of solving this problem is basically the same as the one above. We 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) {
        // 判断 base case
        if (!inArea(grid, i, j)) {
            return 0;
        }
        // 如果这个格子不是岛屿,直接返回
        if (grid[i][j] != 1) {
            return 0;
        }

        //用2来标记已经遍历过
        grid[i][j] = 2; 
        
        // 访问上、下、左、右四个相邻方向
        return 1 + dfs(grid, i - 1, j) + dfs(grid, i + 1, j) + dfs(grid, i, j - 1) + dfs(grid, i, j + 1);
    }

    // 判断坐标 (r, c) 是否在网格中
    boolean inArea(int[][] grid, int i, int j) {
        return 0 <= i && i < grid.length 
                && 0 <= j && j < grid[0].length;
    }
}

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

Let's see how efficient our code is.

Doesn't it look good, yes, that's how it works! ! !

In the end, I wrote this article for almost a week. I don’t know how it was written. However, I tried my best to express what I think clearly. It is mainly a way of thinking and problem solving. There must be many more. For other methods, go to LeetCode to see it.

Well, it's been a long time since I've written it. I'll look at other articles in the next article. I hope it will be helpful to everyone. See you again! !

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

Guess you like

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