4.Binary Tree(with example problem)

0. Basic Concepts

0.1 Binary Tree

  1. At the depth of i, the number of nodes is 2^(i-1) at most.
  2. For a tree whose depth is k, the number of nodes is 2^(k-1) at most

0.2 Full Binary Tree

The binary tree that each node has two or none children.

0.3 Binary Search Tree

For this type of tree, the left child node is smaller than its parent node, while the right child node is greater than its parent node.

0.4 Divide and Conquer:

Due to the feature of binary tree, tree problem can be treated as divide-and-conquer problem.

Three Elements:

  1. Divide: decompose the original problem into same type sub-problem
  2. Conquer: solve sub-problem by recursing. If the it’s the base case, return the answer directly.
  3. Combine/ Merge: Merge the answer of all the problems.

1. Problem Type:

Tips:

  1. For Binary Tree problem, pay attention to the relationship between final result and the results on left/right child.

1.0 Traverse in Binary Tree

Three types of traverse in binary tree: pre-order, in-order, post-order.

1.0.1 Examples:

66. Binary Tree Preorder Traversal

Note here, use the insert() method of vector.

vector<int> preorderTraversal(TreeNode * root) {
        // write your code here
        vector<int> result;
        
        if(!root)
            return result;
        
        
        // Divide
        vector<int> left = preorderTraversal(root ->left);
        vector<int> right = preorderTraversal(root ->right);
        
        
        // Conquer (Merge)
        result.push_back(root->val);
        result.insert(result.end(), left.begin(), left.end());
        result.insert(result.end(), right.begin(), right.end());
        
        return result;
    }

67. Binary Tree Inorder Traversal

68. Binary Tree Postorder Traversal

1.1 DFS:

  1. The implement of DFS in tree is usually recursion. In interview, most tree problem can be handled as recursion.
  2. We usually use Stack to turn the recursion into non-recursion.
1.1.1 Examples:

Maximum Depth of Binary Tree

int maxDepth(TreeNode * root) {
        // write your code here
        
        if(!root)
            return 0;
            
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }

88. Lowest Common Ancestor of a Binary Tree

TreeNode * lowestCommonAncestor(TreeNode * root, TreeNode * A, TreeNode * B) {
        // write your code here
        
        if(!root || A == root || B == root)
            return root;
            
        TreeNode * left = lowestCommonAncestor(root ->left, A, B);
        TreeNode * right = lowestCommonAncestor(root ->right, A, B);
        
        if(left && right)   return root;
        if(left)    return left;
        if(right)   return right;
        
        return nullptr;
    }

596. Minimum Subtree
Write a helper function to decide the current sum of root, and then update the global variable: subtree, subSum.

class Solution {
private:
    TreeNode * subtree = nullptr;
    int subSum = INT_MAX;
public:
    /**
     * @param root: the root of binary tree
     * @return: the root of the minimum subtree
     */
    TreeNode * findSubtree(TreeNode * root) {
        // write your code here
        
        helper(root);
        return subtree;
        
    }
    
    int helper(TreeNode * root)
    {
        if(! root)
            return 0;
            
        int sum = helper(root ->left) + helper(root ->right) + root->val;
        
        if(sum < subSum)
        {
            subSum = sum;
            subtree = root;
        }
        
        return sum;
    }
};

1.2 Level-Traversal/ BFS:

Though BFS was mentioned mostly in graph situation, the tree can be regraded as special tree.

  1. Visit the nodes level by level.
  2. It belongs to the BFS.

However, Level traversal problem usually related to the serialization problem. For example, transform a tree into json format.

1.2.1 Examples:

69. Binary Tree Level Order Traversal

In level traversal problem, there points to notice:

  1. using queue, instead of stack
  2. Use second loop to iterate each level
  3. size = queue.size() to confirm the next level size.
    vector<vector<int>> levelOrder(TreeNode * root) {
        // write your code here
        
        vector<vector<int>> result;
        queue<TreeNode *> stk;
        
        if(root)
            stk.push(root);
        
        while(!stk.empty())
        {
            int size = stk.size();
            vector<int> level_result;
            
            for(int i = 0; i < size; i++)
            {
                TreeNode * tmp = stk.front();
                stk.pop();
                level_result.push_back(tmp->val);
                
                if(tmp->left)   stk.push(tmp->left);
                if(tmp->right)  stk.push(tmp->right);
                
            }
            result.push_back(level_result);
            
        }
        
        return result;
    }

70. Binary Tree Level Order Traversal II
Similar idea with previous problem, except the order. To attain the bottom-up order, we can 1. use push_front() instead of push_back; 2. attain the top-down order, and then reverse the result.

71. Binary Tree Zigzag Level Order Traversal
Level traversal problem, except it need to zigzag. We will first write the level traversal code, then and a bool to check whether current level need to be reversed.

242. Convert Binary Tree to Linked Lists by Depth

It just transform the vector into tree structure.

7. Serialize and Deserialize Binary Tree

tip:
a.note the serialize() function, in the first for-loop, it’s i < queue.size() instead of a const.

class Solution {
public:
    /**
     * This method will be invoked first, you should design your own algorithm 
     * to serialize a binary tree which denote by a root node to a string which
     * can be easily deserialized by your own "deserialize" method later.
     */
    string serialize(TreeNode * root) {
        // write your code here
        if(!root)
            return "{}";
            
        vector<TreeNode* > queue;
        queue.push_back(root);
        
        // 1. store the tree into vector
        for(int i = 0; i < queue.size(); i++){
            if(queue[i] == nullptr)
                continue;
                
            queue.push_back(queue[i] ->left);
            queue.push_back(queue[i] ->right);
        }
        
        // serialize the vector into string
        string result = "";
        result += "{";
        result += to_string(queue[0] ->val);
        for(int i = 1; i < queue.size(); i++){
            if(queue[i] == nullptr){
                result += ",#";
            } else {
                result += ",";
                result += to_string(queue[i] ->val);
            }
        }
        result += "}";
        return result;
    }

    /**
     * This method will be invoked second, the argument data is what exactly
     * you serialized at method "serialize", that means the data is not given by
     * system, it's given by your own serialize method. So the format of data is
     * designed by yourself, and deserialize it here as you serialize it in 
     * "serialize" method.
     */
    TreeNode * deserialize(string &data) {
        // write your code here
        if(data == "{}")
            return nullptr;
            
        vector<string> tokens = split(data.substr(1, data.length() -2), ',');
        TreeNode* root = new TreeNode(stoi(tokens[0]));
        TreeNode* ptr = root;
        queue<TreeNode* > queue;
        queue.push(root);
        
        bool isLeft = true;
        
        for(int i = 1; i < tokens.size(); i++){
            string child = tokens[i];
            
            if(child != "#"){
                TreeNode* tmp = new TreeNode(stoi(tokens[i]));
                
                if(isLeft)
                    queue.front() ->left = tmp;
                else 
                    queue.front() ->right = tmp;
                    
                queue.push(tmp);
            }
            
            if(!isLeft)
                queue.pop();
            
            isLeft = !isLeft;
        }
        return root;
        
    }
    
    vector<string> split(const string& s, char delimiter){
        vector<string> tokens;
        string token;
        istringstream tokenStream(s);
        while(getline(tokenStream, token, delimiter) ){
            tokens.push_back(token);
        }
        return tokens;
    }
    

    
};

1.3 Binary Search Tree (BST):

Definition:

  1. All the left nodes is smaller than root
  2. All the right nodes is larger than root

Characteristic:

  1. The in-order traversal is ascending order
1.3.1 Examples:

95. Validate Binary Search Tree

This problem involves the ResultType class. To validate a BST, we need to know the three things of its left/right subtree, which would be isBST, minNode, maxNode. Thus, we encapsulate these three into Result Type as the result of helper function.

class ResultType {
public:
    bool isBST;
    TreeNode* minNode;
    TreeNode* maxNode;
    ResultType(): isBST(true), minNode(nullptr), maxNode(nullptr){}
};

class Solution {
public:
    /**
     * @param root: The root of binary tree.
     * @return: True if the binary tree is BST, or false
     */
    bool isValidBST(TreeNode * root) {
        // write your code here
        ResultType result = helper(root);
        
        return result.isBST;
    }
    
    ResultType helper(TreeNode* root)
    {
        ResultType result;
        
        if(!root)
            return result;
            
        ResultType left = helper(root ->left);
        ResultType right = helper(root ->right);
        
        if(! left.isBST || ! right.isBST){
            result.isBST = false;
            return result;
        }
        
        if(left.maxNode && left.maxNode->val >= root ->val)
        {
            result.isBST = false;
            return result;
        }
        
        if(right.minNode && right.minNode ->val <= root -> val){
            result.isBST = false;
            return result;
        }
        
        result.isBST = true;
        result.minNode = !left.minNode? root : left.minNode;
        result.maxNode = !right.maxNode? root : right.maxNode;
        
        return result;
        
    
        
    }
};

86. Binary Search Tree Iterator

class BSTIterator {
public:
    stack<TreeNode *> myStack;
    TreeNode *current;
    
    BSTIterator(TreeNode *root) {
        while (!myStack.empty()) {
            myStack.pop();
        }
        current = root;
    }

    /** @return whether we have a next smallest number */
    bool hasNext() {
        return (current != NULL || !myStack.empty());
    }

    /** @return the next smallest number */
    TreeNode* next() {
        while (current != NULL) {
            myStack.push(current);
            current = current->left;
        }
        current = myStack.top(); myStack.pop();
        TreeNode *nxt = current;
        current = current->right;
        return nxt;
    }
};

448. Inorder Successor in BST

TreeNode * inorderSuccessor(TreeNode * root, TreeNode * p) {
        // write your code here
        if(!root)
            return root;
            
        if(root->val <= p->val)
            return inorderSuccessor(root->right, p);
            
        TreeNode* left = inorderSuccessor(root->left, p);
        
        return left? left: root;
    }

11. Search Range in Binary Search Tree

Write a dfs helper function to traverse the tree, push the val into result if it satisfies the requirements.

class Solution {
public:
    /**
     * @param root: param root: The root of the binary search tree
     * @param k1: An integer
     * @param k2: An integer
     * @return: return: Return all keys that k1<=key<=k2 in ascending order
     */
     
    vector<int> result;
    vector<int> searchRange(TreeNode * root, int k1, int k2) {
        // write your code here
        
        if(!root)
            return result;
        dfs(root, k1, k2);
        
        return result;
        
    }
    
    void dfs(TreeNode * root, int k1, int k2) {
        if(!root)
            return;
            
        dfs(root ->left, k1, k2);
        
        if(root ->val >= k1 && root -> val <= k2 )
            result.push_back(root ->val);
            
        dfs(root ->right, k1, k2);
    }
};

85. Insert Node in a Binary Search Tree

TreeNode * insertNode(TreeNode * root, TreeNode * node) {
        // write your code here
        if(!root)
            return node;
        
        TreeNode* current = root, *pre_node = nullptr;
        while(current)
        {
            pre_node = current;
            if(current ->val > node->val)
            {
                current = current ->left;
            }
            else {
                current = current ->right;
            }
        }
        
        if(pre_node ->val > node->val)
            pre_node ->left = node;
        else
            pre_node ->right = node;
        
        return root;
    }

87. Remove Node in Binary Search Tree

The advanced operation on BTS. Pay attention to the replace of the new node. The main idea would be:

  1. Locate the target node. If not exists, return the original one.
  2. Strategically modify the BST.

How to delete the target node and modify the BST? Let the target node be “target”
a. check whether target->right is null
b. if not, check target->right->left is nul
c. if not, find the next in-order node of target

    TreeNode* removeNode(TreeNode* root, int value) {
        // write your code here
        if (root == NULL)
            return NULL;
        TreeNode * head = new TreeNode();
        head->left = root;
        TreeNode * tmp = root, *father = head;

        while (tmp != NULL) {
            if (tmp->val == value)
                break;
            father = tmp;
            if (tmp->val > value)
                tmp = tmp->left;
            else
                tmp = tmp->right;
        }
        if (tmp == NULL)
            return head->left;

        if (tmp->right == NULL) {
            if (father->left == tmp)
                father->left = tmp->left;
            else
                father->right = tmp->left;
        } else 
        if (tmp->right->left == NULL) {
            if (father->left == tmp)
                father->left = tmp->right;
            else
                father->right = tmp->right;

            tmp->right->left = tmp->left;
            
        } else {
            father = tmp->right;
            TreeNode * cur = tmp->right->left;
            while (cur->left != NULL) {
                father = cur;
                cur = cur->left;
            }
            tmp->val = cur->val;
            father->left = cur->right;
        }
        return head->left;
    }

1.4 Tree Path Problem

This kind of problem will need you find out all/a paths in a tree, which satisfy certain requirements.

In this kind problem, we will use path(vector< int>) and result(vector<vector< int>>) as a record to store the path we have.

1.4.1 Examples:

480. Binary Tree Paths

vector<string> binaryTreePaths(TreeNode * root) {
        // write your code here
        vector<string> paths;
        
        if(!root)
            return paths;
            
        if(! root ->left && ! root ->right)
        {
            paths.push_back(to_string(root ->val));
            return paths;
        }
        
        vector<string> left = binaryTreePaths(root ->left);
        vector<string> right = binaryTreePaths(root ->right);
        
        for(auto path : left)
        {
            paths.push_back(to_string(root ->val) + "->" + path);
        }
        
        for(auto path : right)
        {
            paths.push_back(to_string(root ->val) + "->" + path);
        }
        
        return paths;
    }

1.5 Switch Tree between other Data Structure

This kind of problem will need you convert the tree into other structure, e.g. linked list, array. Or vice versa.

If it is the former type–convert tree into other structure, it usually applying DFS or BFS.

If it is the latter type–convert other structure into tree, it usually solves problem by D&C. Recursively divide the data into two subtrees.

1.5.1 Examples:

1534. Convert Binary Search Tree to Sorted Doubly Linked List
Tricky solution.

class Solution {

public:
    TreeNode* treeToDoublyList(TreeNode* root) { 
        // Write your code here.
        if(root == nullptr)
            return nullptr;
        TreeNode* first = nullptr;
        TreeNode* last = nullptr;
        
        treeToDLLUtil(root, &first, &last);
        last->right = first;
        first->left = last;
        return first;
    }
private:
    void treeToDLLUtil(TreeNode* root, TreeNode** first, TreeNode** last){
        if(root == nullptr)
            return;
        treeToDLLUtil(root->left, first, last);
        if(*first == nullptr){
            *first = root;
            *last = root;
        } else {
           (*last)->right = root;
            root->left = *last;
            *last = root;
        }
        treeToDLLUtil(root->right, first, last);
    }
};

453. Flatten Binary Tree to Linked List

class Solution {
public:
    /**
     * @param root: a TreeNode, the root of the binary tree
     * @return: nothing
     */
    void flatten(TreeNode * root) {
        // write your code here
        helper(root);
    }
    
    TreeNode* helper(TreeNode* root)
    {
        if(!root)
            return root;
            
        TreeNode* leftLast = helper(root->left);
        TreeNode* rightLast = helper(root->right);
        
        if(leftLast)
        {
            leftLast->right = root->right;
            root->right = root->left;
            root->left = nullptr;
        }
        
        if(rightLast)
        {
            return rightLast;
        }
        
        if(leftLast)
            return leftLast;
            
        return root;
    }
};

1.6 Search Specific Node

This kind of problem usually input a node, and require to find another node, which can be its pre-oder node, post-order node, left/ right brother node.

If it is a doubly linked list, we could just bottom up by parent pointer. If it is singly linked list, we can only search down by DFS.

1.6.1 Examples:

1.7 Trie Tree

A trie or prefix tree is a 26-ary tree used to retrieve a string, or string prefix, from a collection. Each node of the dictionary tree has an array of pointers to represent all its subtrees, which is essentially a hash table, because the location of the subtree (index) itself represents the letter corresponding to the node.

1.7.1 Examples:

1.8 Binary Tree with Return Type

In this type of problem, it would be more convenient for us to write a customized the return type; e.g.

class ResultType { int var1, var2; }

1.8.1 Example

93. Balanced Binary Tree

This problem is just let you have sense of return type. Sometimes, you don’t have to spend time creating a class; Instead, all you need is to return specific result as your identical result. For example, in this problem, -1 represents it’s not a balanced tree. If it returns a positive number, then it’s the height of the tree.

Thus, as we only need to know whether it’s a balanced tree, we could use -1 as flag to note.

Finally, don’t forget to +1 in the last line of helper function, the result represents the current depth of level.

class Solution {
public:
    /**
     * @param root: The root of binary tree.
     * @return: True if this Binary tree is Balanced, or false.
     */
    bool isBalanced(TreeNode * root) {
        // write your code here
        return helper(root) != -1;
    }
    
    int helper(TreeNode * root)
    {
        if(!root)
            return 0;
            
        int left = helper(root ->left);
        int right = helper(root ->right);
        
        if(left == -1 || right == -1 || abs(left - right) > 1)
            return -1;
            
        return max(left, right) + 1;
    }
};

597. Subtree with Maximum Average

This is good example of ResultType problem. We need to put the necessary information into the Result Type.

Then, in divide and conquer part, merge the result.

class ResultType {
public:
    int sum;
    int size;
    ResultType(): sum(0), size(0){}
    ResultType(int _sum, int _size): sum(_sum), size(_size){}
} ;


class Solution {
private:
    TreeNode* node = nullptr;
    ResultType data;
    
public:
    /**
     * @param root: the root of binary tree
     * @return: the root of the maximum average of subtree
     */
    TreeNode * findSubtree2(TreeNode * root) {
        // write your code here
        helper(root);
        return node;
    }
    
    ResultType helper(TreeNode* root){
        if(!root)
            return ResultType();
            
        // divide 
        ResultType left = helper(root ->left);
        ResultType right = helper(root ->right);
        
        // conquer
        ResultType rootResult = ResultType(
            root ->val + left.sum + right.sum,
            1+ left.size + right.size);
        
        // replace / expression with * expression
        // if there is no result or need to update result
        if(!node || data.sum *rootResult.size < rootResult.sum * data.size ){
            data = rootResult;
            node = root;
        }
        
        return rootResult;
            
    }
    
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值