Leetcode brush questions 05 tree

Posted by rosegarden on Wed, 23 Feb 2022 12:59:08 +0100

character string

Basic knowledge

Definitions and related concepts

1. Linked list and diagram

Single linked list: one data field + one pointer field

Tree: one data field + multiple pointer fields

Figure: vertex set + edge

Tree: acyclic connected graph is a special kind of graph.

2. Definition of tree

A tree is a finite set of N (N > = 0) nodes. When N=0, it is an empty tree, and when N > 0, it shall meet the following requirements:

  • There is and only one specific node called root
  • N> 1, the other nodes can be divided into m (M > 0) disjoint finite sets. Each finite set itself is a tree

3. Related concepts of tree:

  • Root node: the only node in the top layer of the non empty tree, and the other nodes are its descendants
  • Degree of node: the number of child nodes the node has
  • Leaf node: node with degree 0
  • Parent child node: a pair of directly connected nodes. The parent node is in the upper layer and the child node is in the lower layer
  • Sibling node: different nodes generated by the same parent node are called sibling nodes
  • Node hierarchy: it is marked as 1 layer from the root, its child nodes are 2 layers, and its grandchildren are 3 layers
  • Node depth: the number of times the node is in the layer
  • Depth / height of tree: maximum number of layers
  • Node height: the depth / height of the subtree with the node as the root
  • Ordered tree: the new tree obtained by exchanging the positions of the subtree with the sibling node as the root is regarded as a tree different from the original tree
  • Unordered tree: the new tree obtained by exchanging the position of the subtree whose brother node is the root is regarded as the same tree as the original tree

4. Special trees

  • Binary tree: a binary tree is a tree whose degree of each node is no more than 2, and a binary tree is an ordered tree
  • Full binary tree: all leaf nodes are at the bottom, and all non leaf nodes have a degree of 2. It must be a complete binary tree
  • Complete binary tree: the lowest leaf nodes are closely arranged from left to right
  • Binary search tree: it is either an empty tree or satisfies that all node keywords of the left subtree are smaller than those of the root node, all node keywords of the right subtree are larger than those of the root node, and both the left and right subtrees are binary search trees.
  • Balanced binary tree (AVL): if the height difference between the left and right subtrees of each node in the binary tree is not greater than 1, it is a balanced binary tree. It is generally combined with the binary search tree to form a balanced binary search tree, which is used to find data.

Basic operation of tree

1. Storage structure of tree

  • Sequential storage structure: a graph can be represented by an adjacency table, while a tree can be represented by an adjacency table with parent nodes, that is, parent-child node representation, parent node representation and child node representation
  • Linked storage structure: use linked list to represent nodes and their child nodes

2. Addition, deletion, modification and query of tree

In the addition, deletion and modification of the tree, search / search / traversal is the core operation of the tree.

3. Query

Traversal: access each node in the tree according to certain rules to ensure that each node will be "accessed" and each node will be "accessed" only once

"Access": the program interacts with the node or performs some operations on the node

"Enter": the program comes to a node without any interaction with the node

Under different rules, there may be one or more "entry" times for the same node, but "access" to the same node will only occur once.

  • Depth first search DFS of binary tree

Each node can be divided into the "root order" and the "root order" according to the "root order" of the first and second access.

def dfs(TreeNode root):
    if not root:
        return
    # print(root.val) # Preorder traversal 
    dfs(root.left)
    # print(root.val) # Middle order traversal
    dfs(root.right)
    # print(root.val) # Postorder traversal
  • Breadth first search BFS: start from the root node and "access" each node from left to right in the same level from top to bottom. It is also called hierarchy traversal. Each node will only "enter" once.
def bfs(self, root: Optional[TreeNode]):
	q = []
    q.append(root)
    while len(q) and q[0]:
        cur = q.pop()
        print(cur.val)
        if root.left:
            q.append(root.left)
        if root.right:
            a.append(root.right)
    return 


def bfs(self, root: Optional[TreeNode]):
	q = []
    q.append(root)
    while len(q) and q[0]:
        for i in range(len(q)):
            cur = q.pop()
            print(cur.val)
            if root.left:
                q.append(root.left)
            if root.right:
                a.append(root.right)

Topic analysis

Maximum depth of binary tree

1. Title Description

Title Link

2. Analysis ideas and codes

  • Recursion: for each node, its maximum depth is equal to the maximum of its left subtree depth and right subtree depth plus 1
  • Breadth first search: add 1 to the breadth first search template
	public int maxDepth(TreeNode root) {
        if (root == null)   return 0;
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }


	public int maxDepth(TreeNode root) {
        if (root == null)   return 0;
        Queue<TreeNode> queue = new LinkedList<>();
        
        queue.offer(root);
        int ans = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            ans ++ ;
            for (int i = 0; i < size; i ++ ) {
                TreeNode node = queue.poll();
                if (node.left != null)  queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
        }
        return ans;
    }
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1

Convert an ordered array into a binary search tree

1. Title Description

Title Link

2. Problem solving ideas and codes

Idea:

Recursive construction, select the middle value of the array as the root node of the current subtree

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        return helper(nums, 0, nums.length - 1);
    }
    
    public TreeNode helper(int[] nums, int l, int r) {
        if (l > r)  return null;
        
        int mid = l + (r - l) / 2;
        
        TreeNode root = new TreeNode(nums[mid]);
        root.left = helper(nums, l, mid - 1);
        root.right = helper(nums, mid + 1, r);
        return root;
    }
}
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
        def helper(l, r):
            if l > r:
                return None
            mid = int(l + (r - l) / 2)
            root = TreeNode(nums[mid])
            root.left = helper(l, mid - 1)
            root.right = helper(mid + 1, r)
            return root
        return helper(0, len(nums) - 1)

Minimum depth of binary tree

1. Title Description

Title Link

2. Problem solving ideas and codes

Solve recursively to convert the large problem into a sub problem. If the current node is empty, return 0, the left and right nodes of the current node are empty, return 1, the left node of the current node is not empty, calculate the minimum depth of the left node, the right node of the current node is not empty, calculate the minimum depth of the right node, and finally return the minimum depth + 1.

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int minDepth(TreeNode root) {
        if (root == null)   return 0;
        
        if (root.left == null && root.right == null)    return 1;
        int min_depth = Integer.MAX_VALUE;
        if (root.left != null)  min_depth = Math.min(min_depth, minDepth(root.left));
        
        if (root.right != null) min_depth = Math.min(min_depth, minDepth(root.right));
        
        return min_depth + 1;
        
    }

}
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def minDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        if not root.left and not root.right:
            return 1
        min_depth = 10**9
        if root.left:
            min_depth = min(self.minDepth(root.left), min_depth)
        if root.right:
            min_depth = min(self.minDepth(root.right), min_depth)
        return min_depth + 1

Path sum

1. Title Description

Title Link

2. Problem solving ideas and codes

Similarly, use recursive solution to turn a large problem into a small problem. If the current node is empty, return false, and if the left node and right node of the current node are empty, return whether the current sum plus the current node value is equal to targetSum. Otherwise, judge whether the sum of the left subtree or the sum of the right subtree plus the current node value is equal to targetSum.

Optimization: in the above method, three information needs to be saved for each node: current node, current sum and target sum. It can be optimized to convert the total path of the solution tree into the sum of the left subtree or the right subtree, and whether it is equal to targetsum - root Value of val.

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        return helper(root, 0, targetSum);
    }
    
    public boolean helper(TreeNode root, int sum, int targetSum) {
        if (root == null)   return false;
        if (root.left == null && root.right == null)    return sum + root.val == targetSum;
        return helper(root.left, sum + root.val, targetSum) || helper(root.right, sum + root.val, targetSum);
    }
}
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:
            return False
        if not root.left and not root.right:
            return targetSum == root.val
        return self.hasPathSum(root.left, targetSum - root.val) or self.hasPathSum(root.right, targetSum - root.val)

Binary search tree iterator

1. Title Description

Title Link

2. Problem solving ideas and codes

  • Save the middle order traversal results to the array in advance, and then operate on the array
  • Use the stack to maintain the middle order traversal of the binary tree and maintain the stack in real time.
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class BSTIterator {
    private TreeNode cur;
    private Deque<TreeNode> stack;

    public BSTIterator(TreeNode root) {
        cur = root;
        stack = new LinkedList<>();
    }
    
    public int next() {
        while (cur != null) {
            stack.push(cur);
            cur = cur.left;
        }
        cur = stack.pop();
        int res = cur.val;
        cur = cur.right;
        return res;
    }
    
    public boolean hasNext() {
        return cur != null || !stack.isEmpty();
    }
}

/**
 * Your BSTIterator object will be instantiated and called as such:
 * BSTIterator obj = new BSTIterator(root);
 * int param_1 = obj.next();
 * boolean param_2 = obj.hasNext();
 */
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class BSTIterator:

    def __init__(self, root: TreeNode):
        def inorder(root, res):
            if not root:
                return
            inorder(root.left, res)
            res.append(root.val)
            inorder(root.right, res)
        self.arr = list()
        inorder(root, self.arr)
        self.idx = 0
        
    def next(self) -> int:
        res = int(self.arr[self.idx])
        self.idx += 1
        return res

    def hasNext(self) -> bool:
        return self.idx < len(self.arr)


# Your BSTIterator object will be instantiated and called as such:
# obj = BSTIterator(root)
# param_1 = obj.next()
# param_2 = obj.hasNext()

Topics: data structure leetcode