[C++ Code] Balanced binary tree, all paths of the binary tree, the sum of the left leaves--Code Random Notes

Topic: Balanced Binary Tree

  • Given a binary tree, determine whether it is a height-balanced binary tree. A height-balanced binary tree is defined as: the absolute value of the height difference between the left and right subtrees of each node of a binary tree does not exceed 1.
answer
  • The definition of a balanced binary tree in this question is: the absolute value of the height difference between the left and right subtrees of each node of the binary tree does not exceed 1, then the binary tree is a balanced binary tree. According to the definition, a binary tree is a balanced binary tree if and only if all its subtrees are also balanced binary trees. Therefore, you can use recursion to determine whether a binary tree is a balanced binary tree. The order of recursion can be top-down or bottom-up. .

  • Note: The depth of a binary tree node: refers to the number of longest simple path edges from the root node to the node. The height of a binary tree node: refers to the number of longest simple path edges from the node to the leaf node. Regarding whether the depth of the root node is 1 or 0, different places have different standards. In the questions of leetcode, the node is one degree, that is, the depth of the root node is 1. But Wikipedia defines the edge as one degree, that is, the depth of the root node is 0.

  • Because the depth can be checked from top to bottom, preorder traversal (center, left, and right) is required, while the height can only be checked from bottom to top, so only postorder traversal (left, right, middle) is required .

  • For this question, you should understand that since the requirements are relatively high, post-order traversal must be required. Recursive three-step analysis:

    • Clarify the parameters and return values ​​of recursive functions . Parameters: Current incoming node. Return value: The height of the tree with the current incoming node as the root node. So how to mark whether the difference between the left and right subtrees is greater than 1? If the binary tree whose current incoming node is the root node is no longer a binary balanced tree, it would be meaningless to return the height. So if it is no longer a binary balanced tree, you can return -1 to mark that it no longer complies with the rules of a balanced tree.

    • Specify termination conditions . The recursive process is terminated if an empty node is still encountered, and 0 is returned, indicating that the tree height of the current node as the root node is 0

    • Clarify the logic of single-level recursion . How to determine whether the binary tree with the current incoming node as the root node is a balanced binary tree? Of course, it is the difference between the height of its left subtree and the height of its right subtree. Find the height of its left and right subtrees respectively, and then if the difference is less than or equal to 1, return the height of the current binary tree, otherwise -1 is returned, indicating that it is no longer a binary balanced tree.

    • class Solution {
              
              
      public:
          int getHeight(TreeNode *node){
              
              
              if(node==nullptr){
              
              
                  return 0;
              }
              int leftH=getHeight(node->left);
              if(leftH==-1)
                  return -1;
              int rightH=getHeight(node->right);
              if(rightH==-1)
                  return -1;
              return abs(leftH-rightH)>1?-1:1+max(leftH,rightH);
          }
          bool isBalanced(TreeNode* root) {
              
              
              return getHeight(root)==-1?false:true;
          }
      };
      

Topic: All paths in a binary tree

  • Given the root node of a binary tree root, return all paths from the root node to the leaf nodes in any order . Leaf nodes refer to nodes that have no child nodes.
answer
  • The most intuitive way is to use depth-first search. When traversing a binary tree with depth-first search, we need to consider the current node and its children. If the current node is not a leaf node , add the node at the end of the current path and continue to recursively traverse each child node of the node. If the current node is a leaf node , then after adding the node at the end of the current path, we will get a path from the root node to the leaf node, and just add the path to the answer.

  • The path from the root node to the leaves is required, so preorder traversal is required, so that the parent node can point to the child node and find the corresponding path. This question will involve backtracking for the first time, because we need to record the path and need to backtrack to go back one path and then enter another.

  • Insert image description here

  • First use recursion to do preorder traversal. You must know that recursion and backtracking belong to the same family, and this question also requires backtracking .

    • Recursive function parameters and return values ​​must be passed into the root node, record the path of each path, and the result that stores the result set. There is no need for a return value for recursion here.

    • Determine recursion termination conditions. Because this question requires finding leaf nodes, the processing logic of the end begins (putting the path into the result). So when is the leaf node found? When cur is not empty and its left and right children are empty, the leaf node is found. The vector structure path is used here to record the path, so the path of the vector structure must be converted into string format, and then the string must be put into the result. So why is the vector structure used to record the path? Because when processing single-layer recursive logic below, we need to backtrack, and vector is used to facilitate backtracking.

    • Identify single level recursive logic. Because it is a pre-order traversal, the intermediate nodes need to be processed first. The intermediate nodes are the nodes on the path we want to record and put into the path first. Then there is the process of recursion and backtracking. As mentioned above, it is not judged whether cur is empty, so when recursing here, if it is empty, the next level of recursion will not be performed.

  • class Solution {
          
          
    public:
        void traversal(TreeNode* cur,string path,vector<string>& res){
          
          
            path+=to_string(cur->val);
            if(cur->left==nullptr&&cur->right==nullptr){
          
          
                res.push_back(path);
                return;
            }
            if(cur->left){
          
          
                traversal(cur->left,path+"->",res);
            }
            if(cur->right){
          
          
                traversal(cur->right,path+"->",res);
            }
        }
        vector<string> binaryTreePaths(TreeNode* root) {
          
          
            vector<string> res;
            string path;
            if(root==nullptr){
          
          
                return res;
            }
            traversal(root,path,res);
            return res;
        }
    };
    
  • Note that when the function is defined void traversal(TreeNode* cur, string path, vector<string>& result), it is defined string paththat every time it is copied and assigned, there is no need to use a reference, otherwise the backtracking effect will not be achieved. (This involves C++ syntax knowledge)

  • So in the above code, it seems that the logic of backtracking is not seen, but in fact, it is not the case, the backtracking is hidden in traversal(cur->left, path + "->", result);it path + "->". After each function call, "->" is still not added to the path. This is backtracking. If you want to add backtrace, you need to add backtrace on the basis of the above code, and you can AC.

    •     void traversal2(TreeNode *cur,string path,vector<string>& res){
              
              
              path+=to_string(cur->val);
              if(cur->left==nullptr&&cur->right==nullptr){
              
              
                  res.push_back(path);
                  return;
              }
              if(cur->left){
              
              
                  path+="->";
                  traversal2(cur->left,path,res);
                  path.pop_back();// 回溯 '>'
                  path.pop_back();// 回溯 '-'
              }
              if(cur->right){
              
              
                  path+="->";
                  traversal2(cur->right,path,res);
                  path.pop_back();
                  path.pop_back();
              }
          }
      
    • It is okay if used path + "->"as a function parameter, because the value of path has not been changed. After executing the recursive function, path is still the previous value (equivalent to backtracking). Although the second recursive code is streamlined, it contains many important points. Hidden in the code details, although the first recursive writing method has more code, it fully displays every logical processing.

  • In the second version of the code, it is actually only ->part of the backtracking (pop_back is called twice, one pop >and one pop -). You should be wondering why path += to_string(cur->val);this step is not backtracked? Can a path continue to add nodes without backtracking? In fact, the key lies in the parameters. What is used is that string pathno reference is added here, &that is, in this layer of recursion, path + the value of the node, but when the recursion of this layer ends, the value of the path in the previous layer will not be affected in any way .

    • Insert image description here

    • The path of node 4 traverses to node 3, path+3, and after the recursion of traversing node 3 ends, it returns to node 4 (the backtracking process), and path does not add 3. So this is the effect of no reference in the parameters, no address copying, only content copying. (This involves knowledge of C++ references)

    • In the first version, I used references for function parameters, that is vector<int>& path, the address will be copied, so if there is recursive logic at this level, path.push_back(cur->val);it must be corresponding path.pop_back().

    • Then some students may think, why not define a string& pathfunction parameter like this, and then maybe show the backtracking process in the recursive function, but the key is that path += to_string(cur->val);each time a number is added, if the number is a single digit, then it is easy to say , just call it once path.pop_back(), but what if it is tens, hundreds, or thousands? The hundreds digit must be called three times path.pop_back()to achieve the corresponding backtracking operation, so the code implementation is too redundant.

    • Therefore, in the first version of the code, I used a vector type path, so that I can easily demonstrate the backtracking operation in the code. For a vector type path, no matter how many numbers are collected in each path, it must be int, so just one pop_back is enough.

    • // 版本一
      class Solution {
              
              
      private:
          void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) {
              
              
              path.push_back(cur->val); // 中,中为什么写在这里,因为最后一个节点也要加入到path中 
              // 这才到了叶子节点
              if (cur->left == NULL && cur->right == NULL) {
              
              
                  string sPath;
                  for (int i = 0; i < path.size() - 1; i++) {
              
              
                      sPath += to_string(path[i]);
                      sPath += "->";
                  }
                  sPath += to_string(path[path.size() - 1]);
                  result.push_back(sPath);
                  return;
              }
              if (cur->left) {
              
               // 左 
                  traversal(cur->left, path, result);
                  path.pop_back(); // 回溯
              }
              if (cur->right) {
              
               // 右
                  traversal(cur->right, path, result);
                  path.pop_back(); // 回溯
              }
          }
      public:
          vector<string> binaryTreePaths(TreeNode* root) {
              
              
              vector<string> result;
              vector<int> path;
              if (root == NULL) return result;
              traversal(root, path, result);
              return result;
          }
      };
      

Topic: Sum of left leaves

  • Given the root node of a binary tree root, return the sum of all left leaves.
answer
  • Because the question does not actually make it clear what the left leaf is, let me give a clear definition of the left leaf: the left child of node A is not empty, and the left and right children of the left child are empty (indicating that it is a leaf node), Then the left child of node A is the left leaf node. It is impossible to determine whether the current node is a left leaf. You must use the node's parent node to determine whether its left child is a left leaf .

  • The recursive traversal order is post-order traversal (left, center) because the sum of the left leaf values ​​is accumulated through the return value of the recursive function. Recursive trilogy:

    • Determine the parameters and return value of the recursive function: To determine the sum of the left leaf nodes of a tree, you must pass in the root node of the tree. The return value of the recursive function is the sum of the values, so it is int

    • Determine the termination condition: If an empty node is traversed, the left leaf value must be 0; note that only if the currently traversed node is a parent node, can it be determined whether its child node is a left leaf. So if the currently traversed node is a leaf node, then its left leaf must also be 0, then the termination condition is:

    • if (root == NULL) return 0;
      if (root->left == NULL && root->right== NULL) return 0; //其实这个也可以不写,如果不写不影响结果,但就会让递归多进行了一层。
      
    • Determine the logic of single-level recursion: when encountering a left leaf node, record the value, and then use recursion to find the sum of the left leaves of the left subtree and the sum of the left leaves of the right subtree. The sum is the left leaf of the entire tree. Sum.

    • class Solution {
              
              
      public:
          int sumOfLeftLeaves(TreeNode* root) {
              
              
              if(root==nullptr){
              
              return 0;}
              if(root->left==nullptr&&root->right==nullptr){
              
              
                  return 0;
              }
              int leftV=sumOfLeftLeaves(root->left);
              if(root->left&&!root->left->left&&!root->left->right){
              
              
                  leftV=root->left->val;
              }
              int rightV=sumOfLeftLeaves(root->right);
              int sum=leftV+rightV;
              return sum;
          }
      };
      
  • A node is a "left leaf" node if and only if it is the left child of a node and it is a leaf node . Therefore, we can consider traversing the entire tree. When we traverse to the node node, if its left child node is a leaf node, then the value of its left child node is accumulated and included in the answer.

    • class Solution {
              
              
      public:
          bool isleaf(TreeNode* node){
              
              
              return !node->left&&!node->right;
          }
          int dfs(TreeNode *node){
              
              
              int ans=0;
              if(node->left){
              
              
                  ans+=isleaf(node->left)?node->left->val:dfs(node->left);
              }
              if(node->right&&!isleaf(node->right)){
              
              
                  ans+=dfs(node->right);
              }
              return ans;
          }
          int sumOfLeftLeaves(TreeNode* root) {
              
              
              return root?dfs(root):0;
          }
      };
      
    • Time complexity: O(n), where n is the number of nodes in the tree. Space complexity: O(n). Space complexity is related to the maximum depth of the stack used by depth-first search. In the worst case, the tree presents a chain structure with a depth of O(n), and the corresponding space complexity is also O(n).

  • breadth-first traversal

    •         if(!root){
              
              
                  return 0;
              }
              queue<TreeNode*> q;
              q.push(root);
              int ans=0;
              while(!q.empty()){
              
              
                  TreeNode* node=q.front();
                  q.pop();
                  if(node->left){
              
              
                      if(isleaf(node->left)){
              
              
                          ans+=node->left->val;
                      }else{
              
              
                          q.push(node->left);
                      }
                  }
                  if(node->right){
              
              
                      if(!isleaf(node->right)){
              
              
                          q.push(node->right);
                      }
                  }
              }
              return ans;
      
    • Time complexity: O(n), where n is the number of nodes in the tree. Space complexity: O(n). The space complexity is related to the required capacity of the queue used by breadth-first search, which is O(n).

Guess you like

Origin blog.csdn.net/weixin_43424450/article/details/133161409