"Li Kou" 105 + 106. Construct a binary tree from pre order / post order and middle order traversal sequences

(1) Problem solving ideas

The solution ideas of the two questions are the same. See the video of LeetCode problem solution area in detail, which is summarized as follows:

(2) Basic code

The basic code uses the new array generated by copying after splitting on the basis of the original array, and takes the new array variable as the formal parameter of the recursive function.

Constantly recursively copy and generate new segmented arrays. Taking the new arrays as formal parameters has the advantages of simple program and clear logic. The disadvantage is that the operation of copying new variables leads to additional time and space complexity, which is summarized in the following (IV)

Here is the basic code:

Pre sequence + middle sequence

class Solution(object):
    def buildTree(self, preorder, inorder):
        :type preorder: List[int]
        :type inorder: List[int]
        :rtype: TreeNode
        def create(preo, ino):
            if len(preo) == 0:			# Recursion termination condition: pre order or mid order traversal subarray length = = 0
            root = TreeNode(preo[0])	# The first element of the preorder traversal is the root node
            mid = ino.index(preo[0])	# Use the index method to find elements instead of hash tables
            root.left = create(preo[1: mid + 1], ino[0: mid])	# Interval division
            root.right = create(preo[mid + 1:], ino[mid+1:])	# Interval division
            return root
        return create(preorder, inorder)

Post order + middle order

class Solution(object):
    def buildTree(self, inorder, postorder):
        :type inorder: List[int]
        :type postorder: List[int]
        :rtype: TreeNode
        def create(ino, posto):
            if len(ino) == 0:
            root = TreeNode(posto[-1])
            mid = ino.index(posto[-1])
            root.left = create(ino[0:mid], posto[0:mid])
            root.right = create(ino[mid+1:], posto[mid:-1])
            return root
        return create(inorder, postorder)


The main code differences between the two questions are:

  1. The nodes that generate the root node are different - 105: preo[0]; 106: posto[-1]
  2. The sub interval division positions of the pre sequence and post sequence are different - as shown in the figure

(3) Optimize code

The code of the official answer uses many index numbers as formal parameters instead of passing in the entire copied array. The advantages and disadvantages of this are as follows:

  • Disadvantages: the code is not clear and easy to understand, and the index does not look direct
  • Advantages: greatly reduce the time and space complexity

It is discussed in detail in (IV), and the code is as follows:

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        def myBuildTree(preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int):
            if preorder_left > preorder_right:
                return None
            # The first node in the preorder traversal is the root node
            preorder_root = preorder_left
            # Locate the root node in the middle order traversal
            inorder_root = index[preorder[preorder_root]]
            # First establish the root node
            root = TreeNode(preorder[preorder_root])
            # Get the number of nodes in the left subtree
            size_left_subtree = inorder_root - inorder_left
            # The left subtree is constructed recursively and connected to the root node
            # The elements "size_left_subtree starting from the left boundary + 1" in the preorder traversal correspond to the elements "from the left boundary to the root node location - 1" in the inorder traversal
            root.left = myBuildTree(preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1)
            # The right subtree is constructed recursively and connected to the root node
            # The element "starting from the left boundary + 1 + number of left subtree nodes to the right boundary" in the preorder traversal corresponds to the element "locating from the root node + 1 to the right boundary" in the inorder traversal
            root.right = myBuildTree(preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right)
            return root
        n = len(preorder)
        # Construct hash mapping to help us quickly locate the root node
        index = {element: i for i, element in enumerate(inorder)}
        return myBuildTree(0, n - 1, 0, n - 1)
Source: force buckle( LeetCode)

(4) Summary of code optimization methods to reduce complexity

After reading the official solution of question 105 and discussing the following question about "why do formal parameters make so many non intuitive index es", we found the beauty of the official solution, mainly including the following two points:

(1) Tip 1: take the index of the array as a formal parameter, not the array itself

  • If the parameter is a sublist of pre and inorder, the advantage is that it is easy to understand, but it needs to constantly copy new sublists from the original sequence in the recursive process. This operation leads to additional space and time consumption, resulting in the space and time complexity of the recursive function being O(k), (k is the number of elements copied in the current state)
  • If the parameter is the element index of pre and inorder and corresponds to the original sequence (the operation corresponding to the original sequence is implemented by hash table), there is no array copy operation at all. Although it needs to think about the corresponding relationship between the index and the sub sequence, the time and space complexity of the sub module are reduced from O(k) to O(1)
  • Further, because this is a recursive process, the complexity of O(k) of the recursive function module will lead to the factorial sub complexity of the overall O(k!) of the main function

(2) Skill 2: use hash table to reduce array lookup complexity

The purpose of using hash table is to reduce the time complexity of array lookup

The official solution is described as follows:

Therefore, the following operations are done in the problem solution:

	# Construct hash mapping to help us quickly locate the root node
	index = {element: i for i, element in enumerate(inorder)}

(3) Summary: the essence of programming - pointer

  • From the above reasons, we can see that the ingenious use of pointers can reduce the time and space complexity to the constant level. Therefore, when designing and optimizing programs, we can consider using the combination of pointers and hash tables to find the target value from large data structures
  • The pointer operation here has the feeling of "looking for books everywhere in the library according to the serial number", which is very wonderful

(5) More attention

(1) Iterative solution

When looking at LeetCode's answers, I found that the top few that took the least time were basically iterative methods rather than recursion. I have seen relevant statements before that "iterative methods are closer to the computational essence of computers, and recursion is actually a great waste of resources"

Therefore, there are the following problems:

  • Is iterative method less time-consuming than recursion? Is there a theoretical basis?
  • Is it best to write iterations when programming?
  • Will iterative and recursive solutions be required during the machine test interview? Do you need to master iteration?

Tags: Python Algorithm data structure

Posted on Fri, 19 Nov 2021 09:26:30 -0500 by Zag0r