Data structure refresher: Day 11

Table of contents

 1. Level-order traversal of a binary tree

 1. Breadth-first search

Ideas and algorithms

Complexity analysis

2. The maximum depth of a binary tree

 1. Depth-first search

Ideas and Algorithms

Complexity analysis

2. Breadth-first search

Ideas and Algorithms

Complexity analysis

3. Symmetric binary tree

 1. Recursion

Ideas and algorithms

Complexity analysis

2. Iteration

Ideas and algorithms

Complexity analysis

 1. Level-order traversal of a binary tree

102. Binary tree level order traversal - LeetCode https://leetcode.cn/problems/binary-tree-level-order-traversal/?plan=data-structures&plan_progress=ggfacv7

 1. Breadth-first search

Ideas and algorithms

We can solve this problem using breadth-first search.

The simplest way we can think of is to use a tuple (node, level) to represent the state, which represents a node and the level it is in. The level value of each new node in the queue is the level of the parent node. Value plus one. Finally, the points are classified according to the level of each point. When classifying, we can use a hash table to maintain an array with level as the key and the corresponding node value as the value. After the breadth-first search is completed, press the key level to retrieve all Value, just form the answer and return it.

Consider how to optimize space overhead: How to implement this function without using hash mapping and using only one variable node to represent the state?

We can modify breadth-first search in a clever way:

First, the root element is entered into the queue.
When the queue is not empty,
find the length of the current queue s_i.
Take s_i elements from the queue to expand, and then enter the next iteration.

The difference between it and ordinary breadth-first search is that ordinary breadth-first search only takes one element at a time to expand, while here it takes s_i
​elements at a time. In the i-th iteration of the above process, the s_i elements of the i-th level of the binary tree are obtained.

Why is this the right thing to do? We observe this algorithm and can summarize the following loop invariant: before the i-th iteration, all elements in the queue are all elements of the i-th layer, and are arranged in order from left to right. Prove its three properties (you can also understand it as mathematical induction):

Initialization: When i=1, there is only root in the queue, which is the only element with a level of 1. Since there is only one element, it obviously satisfies "arrangement from left to right"; maintenance: if the property is true when i=k
, That is, the elements dequeued s_k in the k-th round are all elements of the k-th layer, and the order is from left to right. Because when performing a breadth-first search on the tree, the points extended from the points in the lower k levels must only be points in the k+1 level, and the points in the k+1 level can only be extended from the points in the kth level, so These s_k points can be expanded to all s_k+1 points in the next layer. And because of the first-in-first-out (FIFO) characteristic of the queue, since the dequeuing order of points on the kth layer is from left to right, then the k+1th layer must also be from left to right. At this point, we have been able to prove the correctness of the loop invariant through mathematical induction.

Termination: Because the loop invariant is correct, the result of each iteration after iterating according to this method is the level traversal result of the current layer. At this point, we have proven that the algorithm is correct.

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector <vector <int>> ret;
        if (!root) {
            return ret;
        }

        queue <TreeNode*> q;
        q.push(root);
        while (!q.empty()) {
            int currentLevelSize = q.size();
            ret.push_back(vector <int> ());
            for (int i = 1; i <= currentLevelSize; ++i) {
                auto node = q.front(); q.pop();
                ret.back().push_back(node->val);
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
            }
        }
        
        return ret;
    }
};

Complexity analysis

Let the number of all nodes in the tree be n.

Time complexity: Each point enters and exits the queue once, so the asymptotic time complexity is O(n).


Space complexity: The number of elements in the queue does not exceed n, so the asymptotic space complexity is O(n).

go check this out

Summary of BFS usage scenarios: level-order traversal, shortest path problem - level-order traversal of binary trees - LeetCode https://leetcode.cn/problems/binary-tree-level-order-traversal/solution/bfs-de -shi-yong-chang-jing-zong-jie-ceng-xu-bian-l/

2. The maximum depth of a binary tree

104. Maximum depth of binary tree - LeetCode https://leetcode.cn/problems/maximum-depth-of-binary-tree/?plan=data-structures&plan_progress=ggfacv7

 1. Depth-first search

Ideas and Algorithms

If we know the maximum depth ll and rr of the left subtree and right subtree, then the maximum depth of the binary tree is max(l,r)+1

The maximum depth of the left subtree and right subtree can be calculated in the same way. Therefore, we can use the "depth-first search" method to calculate the maximum depth of the binary tree. Specifically, when calculating the maximum depth of the current binary tree, you can first recursively calculate the maximum depth of its left subtree and right subtree, and then calculate the maximum depth of the current binary tree in O(1) time. Recursion exits when an empty node is reached.

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == nullptr) return 0;
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }
};

Complexity analysis

Time complexity: O(n), where n is the number of binary tree nodes. Each node is traversed only once in the recursion.

Space complexity: O(height), where height represents the height of the binary tree. Recursive functions require stack space, and the stack space depends on the depth of the recursion, so the space complexity is equivalent to the height of the binary tree.

2. Breadth-first search

Ideas and Algorithms

We can also use the "breadth-first search" method to solve this problem, but we need to make some modifications to it. At this time, our breadth-first search queue stores "all nodes of the current layer". Each time we expand the next layer, unlike breadth-first search, which only takes out one node from the queue at a time, we need to take out all the nodes in the queue for expansion, so as to ensure that the queue is expanded every time Stored here are all the nodes of the current layer, that is, we expand layer by layer. Finally, we use a variable ans to maintain the number of expansions. The maximum depth of the binary tree is ans.

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == nullptr) return 0;
        queue<TreeNode*> Q;
        Q.push(root);
        int ans = 0;
        while (!Q.empty()) {
            int sz = Q.size();
            while (sz > 0) {
                TreeNode* node = Q.front();Q.pop();
                if (node->left) Q.push(node->left);
                if (node->right) Q.push(node->right);
                sz -= 1;
            }
            ans += 1;
        } 
        return ans;
    }
};

Complexity analysis

Time complexity: O(n), where n is the number of nodes of the binary tree. The same analysis as method 1, each node will only be visited once.

Space complexity: The space consumption of this method depends on the number of elements stored in the queue, which will reach O(n) in the worst case.

3. Symmetric binary tree

101. Symmetric Binary Tree - LeetCode https://leetcode.cn/problems/symmetric-tree/?plan=data-structures&plan_progress=ggfacv7

 1. Recursion

Ideas and algorithms

A tree is symmetrical if its left subtree is a mirror image of its right subtree.

Therefore, the question can be transformed into: under what circumstances are two trees mirror images of each other?

Two trees are mirror images of each other if the following conditions are met at the same time:

Their two root nodes have the same value.
The right subtree of each tree is a mirror image of the left subtree of the other tree.


We can implement such a recursive function to traverse the tree by "synchronously moving" two pointers. The p pointer and the q pointer both point to the root of the tree at the beginning. Then when p moves to the right, q moves to the left, and p When moving left, q moves right. Each time, check whether the values ​​of the current p and q nodes are equal, and if so, determine whether the left and right subtrees are symmetrical.

class Solution {
public:
    bool check(TreeNode *p, TreeNode *q) {
        if (!p && !q) return true;
        if (!p || !q) return false;
        return p->val == q->val && check(p->left, q->right) && check(p->right, q->left);
    }

    bool isSymmetric(TreeNode* root) {
        return check(root, root);
    }
};

Complexity analysis

Assume there are n nodes in the tree.

Time complexity: This tree is traversed here, and the asymptotic time complexity is O(n).
Space complexity: The space complexity here is related to the stack space used by recursion. The number of recursion levels here does not exceed n, so the asymptotic space complexity is O(n).

2. Iteration

Ideas and algorithms

In "Method 1", we used the recursive method to realize the symmetry judgment, so how to use the iterative method to realize it? First we introduce a queue, which is a common method to rewrite recursive programs into iterative programs. During initialization we enqueue the root node twice. Extract two nodes each time and compare their values ​​(every two consecutive nodes in the queue should be equal, and their subtrees are mirror images of each other), and then press the left and right sub-nodes of the two nodes in reverse order Inserted into the queue in order. The algorithm ends when the queue is empty, or when we detect tree asymmetry (i.e. two unequal consecutive nodes are taken from the queue).

class Solution {
public:
    bool check(TreeNode *u, TreeNode *v) {
        queue <TreeNode*> q;
        q.push(u); q.push(v);
        while (!q.empty()) {
            u = q.front(); q.pop();
            v = q.front(); q.pop();
            if (!u && !v) continue;
            if ((!u || !v) || (u->val != v->val)) return false;

            q.push(u->left); 
            q.push(v->right);

            q.push(u->right); 
            q.push(v->left);
        }
        return true;
    }

    bool isSymmetric(TreeNode* root) {
        return check(root, root);
    }
};

Complexity analysis

Time complexity: O(n), same as "Method 1".
Space complexity: A queue is needed to maintain nodes. Each node can enter the queue once and leave the queue once. There will be no more than n points in the queue, so the asymptotic space complexity is O(n).

Guess you like

Origin blog.csdn.net/m0_63309778/article/details/126844439