The eighth day of algorithm review: Breadth-first search/Depth-first search--2

Table of contents

1. Merge binary trees 

1. Depth-first search

Complexity analysis

2. Breadth-first search

Complexity analysis

Second, fill in the next right node pointer of each node

 1. Level traversal:

Complexity analysis

2. Use the established next pointer

Ideas

algorithm

Complexity analysis

1. Merge binary trees 

617. Merge binary trees - LeetCode https://leetcode.cn/problems/merge-two-binary-trees/?plan=algorithms&plan_progress=gzwnnxs

1. Depth-first search


Two binary trees can be merged using depth-first search. Traverse two binary trees simultaneously starting from the root node and merge the corresponding nodes.

The corresponding nodes of two binary trees may exist in the following three situations, and different merging methods are used for each situation.

If the corresponding nodes of the two binary trees are empty, the corresponding nodes of the merged binary tree will also be empty;

If only one of the corresponding nodes of the two binary trees is empty, the corresponding node of the merged binary tree will be the non-empty node;

If the corresponding nodes of the two binary trees are not empty, the value of the corresponding node of the merged binary tree is the sum of the values ​​of the corresponding nodes of the two binary trees. In this case, the two nodes need to be merged explicitly.

After merging a node, the left and right subtrees of the node must be merged respectively. This is a recursive process.

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (t1 == nullptr) {
            return t2;
        }
        if (t2 == nullptr) {
            return t1;
        }
        auto merged = new TreeNode(t1->val + t2->val);
        merged->left = mergeTrees(t1->left, t2->left);
        merged->right = mergeTrees(t1->right, t2->right);
        return merged;
    }
};

Complexity analysis

Time complexity: O(min(m,n)), where m and n are the number of nodes of the two binary trees respectively. Depth-first search is performed on two binary trees at the same time. Only when the corresponding nodes in the two binary trees are not empty, the node will be explicitly merged. Therefore, the number of nodes visited will not exceed the number of the smaller binary tree. Number of nodes.

Space complexity: O(min(m,n)), where m and n are the number of nodes of the two binary trees respectively. The space complexity depends on the number of levels of recursive calls. The number of levels of recursive calls will not exceed the maximum height of the smaller binary tree. In the worst case, the height of the binary tree is equal to the number of nodes.

2. Breadth-first search


You can also use breadth-first search to merge two binary trees. First, determine whether the two binary trees are empty. If both binary trees are empty, the merged binary tree will also be empty. If only one binary tree is empty, the merged binary tree will be another non-empty binary tree.

If both binary trees are not empty, first calculate the value of the merged root node, then start a breadth-first search from the root nodes of the merged binary tree and the two original binary trees, and traverse each binary tree simultaneously starting from the root node, and Merge the corresponding nodes.

Three queues are used to store the nodes of the merged binary tree and the nodes of the two original binary trees respectively. Initially, the root node of each binary tree is added to the corresponding queue. Each time one node is taken out from each queue, it is judged whether the left and right child nodes of the two original binary tree nodes are empty. If the left child node of at least one node in the current nodes of the two original binary trees is not empty, then the left child node of the corresponding node of the merged binary tree is not empty either. The same goes for the right child node.

If the left child node of the merged binary tree is not empty, the left child node of the merged binary tree and the entire left subtree need to be calculated based on the left child nodes of the two original binary trees. Consider the following two scenarios:

If the left child nodes of both original binary trees are not empty, the value of the left child node of the merged binary tree is the sum of the values ​​of the left child nodes of the two original binary trees, after the left child node of the merged binary tree is created , add the left child node in each binary tree to the corresponding queue;

If one of the left child nodes of the two original binary trees is empty, that is, the left subtree of one original binary tree is empty, then the left subtree of the merged binary tree is the left subtree of the other original binary tree. At this time, there is no It is necessary to continue traversing the non-empty left subtree, so there is no need to add the left child node to the queue.

For the right child node and right subtree, the processing method is the same as for the left child node and left subtree.

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (t1 == nullptr) {
            return t2;
        }
        if (t2 == nullptr) {
            return t1;
        }
        auto merged = new TreeNode(t1->val + t2->val);
        auto q = queue<TreeNode*>();
        auto queue1 = queue<TreeNode*>();
        auto queue2 = queue<TreeNode*>();
        q.push(merged);
        queue1.push(t1);
        queue2.push(t2);
        while (!queue1.empty() && !queue2.empty()) {
            auto node = q.front(), node1 = queue1.front(), node2 = queue2.front();
            q.pop();
            queue1.pop();
            queue2.pop();
            auto left1 = node1->left, left2 = node2->left, right1 = node1->right, right2 = node2->right;
            if (left1 != nullptr || left2 != nullptr) {
                if (left1 != nullptr && left2 != nullptr) {
                    auto left = new TreeNode(left1->val + left2->val);
                    node->left = left;
                    q.push(left);
                    queue1.push(left1);
                    queue2.push(left2);
                } else if (left1 != nullptr) {
                    node->left = left1;
                } else if (left2 != nullptr) {
                    node->left = left2;
                }
            }
            if (right1 != nullptr || right2 != nullptr) {
                if (right1 != nullptr && right2 != nullptr) {
                    auto right = new TreeNode(right1->val + right2->val);
                    node->right = right;
                    q.push(right);
                    queue1.push(right1);
                    queue2.push(right2);
                } else if (right1 != nullptr) {
                    node->right = right1;
                } else {
                    node->right = right2;
                }
            }
        }
        return merged;
    }
};

Complexity analysis

Time complexity: O(min(m,n)), where m and n are the number of nodes of the two binary trees respectively. A breadth-first search is performed on two binary trees at the same time. The node will be accessed only when the corresponding nodes in the two binary trees are not empty, so the number of nodes visited will not exceed the number of nodes in the smaller binary tree.

Space complexity: O(min(m,n)), where m and n are the number of nodes of the two binary trees respectively. The space complexity depends on the number of elements in the queue, which will not exceed the number of nodes in the smaller binary tree.

Second, fill in the next right node pointer of each node

116. Populating the next right node pointer of each node - LeetCode https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/?plan=algorithms&plan_progress=gzwnnxs

 1. Level traversal:

The question itself requires us to connect the nodes at each level of the binary tree to form a linked list. Therefore, the intuitive way is that we can perform hierarchical traversal of the binary tree. During the hierarchical traversal process, we will take out the nodes at each level of the binary tree, traverse and connect them.

Hierarchical traversal is based on breadth-first search. The difference between it and breadth-first search is that breadth-first search will only take out one node for expansion at a time, while hierarchical traversal will take out all elements in the queue for expansion each time. This can It is guaranteed that the elements taken out of the queue and traversed each time belong to the same layer, so we can modify the next pointer of each node during the traversal process and expand the new queue of the next layer.

class Solution {
public:
    Node* connect(Node* root) {
        if (root == nullptr) {
            return root;
        }
        
        // 初始化队列同时将第一层节点加入队列中,即根节点
        queue<Node*> Q;
        Q.push(root);
        
        // 外层的 while 循环迭代的是层数
        while (!Q.empty()) {
            
            // 记录当前队列大小
            int size = Q.size();
            
            // 遍历这一层的所有节点
            for(int i = 0; i < size; i++) {
                
                // 从队首取出元素
                Node* node = Q.front();
                Q.pop();
                
                // 连接
                if (i < size - 1) {
                    node->next = Q.front();
                }
                
                // 拓展下一层节点
                if (node->left != nullptr) {
                    Q.push(node->left);
                }
                if (node->right != nullptr) {
                    Q.push(node->right);
                }
            }
        }
        
        // 返回根节点
        return root;
    }
};

Complexity analysis

Time complexity: O(N). Each node will be visited once and only once, that is, it will be popped from the queue and the next pointer will be established.

Space complexity: O(N). This is a perfect binary tree whose last level contains N/2 nodes. The complexity of breadth-first traversal depends on the maximum number of elements at a level. In this case the space complexity is O(N).

2. Use the established next pointer


Ideas

1) In a tree, there are two types of next pointers.

The first case is to connect two child nodes of the same parent node. They can be accessed directly through the same node, so perform the following operations to complete the connection.

node.left.next = node.right


2) The second case establishes a connection between child nodes of different fathers. In this case, direct connection is not possible.

If each node has a pointer to its parent node, the next node can be found through this pointer. If the pointer does not exist, the connection is established according to the following ideas:

After the next pointers are established between the Nth level nodes, the next pointers of the N+1th level nodes are established. All nodes in the same layer can be accessed through the next pointer, so the next pointer of the Nth layer can be used to establish a next pointer for the N+1th layer node.

algorithm

1) Starting from the root node, since there is only one node in layer 0, there is no need to connect, just create a next pointer for the node in layer 1 directly. One thing to note in this algorithm is that when we establish the next pointer for the Nth layer node, we are at the N−1th layer. When all the next pointers of the Nth layer nodes are established, move to the Nth layer and establish the next pointers of the N+1th layer nodes.

2) When traversing the nodes of a certain layer, the next pointer of the node of this layer has been established. Therefore, we only need to know the leftmost node of this layer, and we can traverse it in a linked list without using a queue.

3) The pseudocode of the above idea is as follows:

leftmost = root
while (leftmost.left != null) {
    head = leftmost
    while (head.next != null) {
        1) Establish Connection 1
        2) Establish Connection 2 using next pointers
        head = head.next
    }
    leftmost = leftmost.left
}

4) Two types of next pointers.

        1. In the first case, the two child nodes belong to the same parent node, so the next pointers of the two child nodes can be established directly through the parent node.

node.left.next = node.right

        2. The second case is the case of connecting child nodes between different parent nodes. More specifically, what is connected is the right child of the first parent node and the left child of the second parent node. Since the next pointer has been established at the parent node level, the second parent node can be found directly through the next pointer of the first parent node, and then a connection is established between their children.

node.right.next = node.next.left

5) After completing the connection of the current layer, enter the next layer and repeat the operation until all nodes are connected. After entering the next layer, you need to update the leftmost node, and then traverse all nodes in the layer starting from the new leftmost node. Because it is a perfect binary tree, the leftmost node must be the left child of the leftmost node of the current layer. If the left child of the current leftmost node does not exist, it means that the last level of the tree has been reached and the connection of all nodes has been completed.

class Solution {
public:
    Node* connect(Node* root) {
        if (root == nullptr) {
            return root;
        }
        
        // 从根节点开始
        Node* leftmost = root;
        
        while (leftmost->left != nullptr) {
            
            // 遍历这一层节点组织成的链表,为下一层的节点更新 next 指针
            Node* head = leftmost;
            
            while (head != nullptr) {
                
                // CONNECTION 1
                head->left->next = head->right;
                
                // CONNECTION 2
                if (head->next != nullptr) {
                    head->right->next = head->next->left;
                }
                
                // 指针向后移动
                head = head->next;
            }
            
            // 去下一层的最左的节点
            leftmost = leftmost->left;
        }
        
        return root;
    }
};

Complexity analysis

  • Time complexity: O(N), each node is only visited once.

  • Space complexity: O(1), no need to store additional nodes.

Guess you like

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