leetcode essence of algorithm interview in Dachang 21. Tree

leetcode essence of algorithm interview in Dachang 21. Tree

Video Explanation (efficient learning): Click to learn

catalog:

1. Introduction

2. Time and space complexity

3. Dynamic planning

4. Greed

5. Binary search

6. Depth first & breadth first

7. Double pointer

8. Sliding window

9. Bit operation

10. Recursion & divide and conquer

11 Pruning & backtracking

12. Reactor

13. Monotone stack

14. Sorting algorithm

15. Linked list

16.set&map

17. Stack

18. Queue

19. Array

20. String

21. Trees

22. Dictionary tree

23. Consolidation

24. Other types of questions

The data structure of tree includes root node, left and right nodes. In the subtree, there are parent nodes, child nodes and brother nodes. Those without child nodes become leaf nodes. The tree is divided into binary tree and multi tree

List is a specialized tree, and tree is a specialized Graph

Binary search tree

Binary Search Tree (English: Binary Search Tree), also known as ordered binary tree or sorted binary tree. The following conditions are met:

  • If its left subtree is not empty, the values of all nodes on the left subtree are less than its root node.
  • If its right subtree is not empty, the values of all nodes on the right subtree are greater than its root node.
  • Its left and right subtrees are also binary search trees.

Advantages of binary search tree: it can not only find data, but also insert and delete data efficiently

Note that the binary search tree is not necessarily a complete binary tree

Tree traversal

  • Depth first
  • Breadth first

  1. Preamble: root left right
  2. Middle order: left root right
  3. Post order: left right root

144. Preorder traversal of binary tree(easy)

94. Middle order traversal of binary tree (easy)

145. Post order traversal of binary tree (easy)

The animation is too large. Click to view it

The animation is too large. Click to view it

The animation is too large. Click to view it

The animation is too large. Click to view it

Method 1. Recursion

js:

//Time complexity O(n),n is a tree of nodes, and space complexity O(n), that is, recursive space overhead (tree height). In the worst case, the tree is a linked list, so it is O(n), and the average space complexity O(logn)
//Preorder traversal:
var preorderTraversal = function(root, res = []) {
    if (!root) return res;
    res.push(root.val);
    preorderTraversal(root.left, res)
    preorderTraversal(root.right, res)
    return res;
};

//Middle order traversal:
var inorderTraversal = function(root, res = []) {
    if (!root) return res;
    inorderTraversal(root.left, res);
    res.push(root.val);
    inorderTraversal(root.right, res);
    return res;
};

//Post order traversal:
var postorderTraversal = function(root, res = []) {
    if (!root) return res;
    postorderTraversal(root.left, res);
    postorderTraversal(root.right, res);
    res.push(root.val);
    return res;
};

Java:

//Time complexity O(n),n is a tree of nodes, and space complexity O(n), that is, recursive space overhead (tree height). In the worst case, the tree is a linked list, so it is O(n), and the average space complexity O(logn)
//Preorder traversal
class Solution {
    ArrayList<Integer> preOrderReverse(TreeNode root) {
        ArrayList<Integer> result = new ArrayList<Integer>();
        preOrder(root, result);
        return result;
    }

    void preOrder(TreeNode root, ArrayList<Integer> result) {
        if (root == null) {
            return;
        }
        result.add(root.val);
        preOrder(root.left, result);
        preOrder(root.right, result);
    }
}
// Medium order traversal
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        inorder(root, res);
        return res;
    }

    void inorder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return;
        }
        inorder(root.left, list);
        list.add(root.val);
        inorder(root.right, list);
    }
}
// Postorder traversal
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        postorder(root, res);
        return res;
    }

    void postorder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return;
        }
        postorder(root.left, list);
        postorder(root.right, list);
        list.add(root.val); 
    }
}

Method 2. Iteration

js:

// The time complexity O(n),n is the node tree, and the space complexity O(n), displays the space overhead of the stack

// Preorder traversal: left and right
// Stack pressing order: right, left, middle
var preorderTraversal = function(root, res = []) {
    const stack = [];
    if (root) stack.push(root);
    while(stack.length) {
        const node = stack.pop();
        if(!node) {
            res.push(stack.pop().val);
            continue;
        }
        if (node.right) stack.push(node.right); // right
        if (node.left) stack.push(node.left); // Left
        stack.push(node); // in
        stack.push(null);
    }; 
    return res;
};


//  Middle order traversal: left, middle, right
//  Stack pressing order: right, middle and left
var inorderTraversal = function(root, res = []) {
    const stack = [];
    if (root) stack.push(root);
    while(stack.length) {
        const node = stack.pop();
        if(!node) {
            res.push(stack.pop().val);
            continue;
        }
        if (node.right) stack.push(node.right); // right
        stack.push(node); // in
        stack.push(null);
        if (node.left) stack.push(node.left); // Left
    };
    return res;
};

// Subsequent traversal: left and right middle
// Stack pressing order: middle right left
var postorderTraversal = function(root, res = []) {
    const stack = [];
    if (root) stack.push(root);
    while(stack.length) {
        const node = stack.pop();
        if(!node) {
            res.push(stack.pop().val);
            continue;
        }
        stack.push(node); // in
        stack.push(null);
        if (node.right) stack.push(node.right); // right
        if (node.left) stack.push(node.left); // Left
    };
    return res;
};

Java:

// The time complexity O(n),n is the node tree, and the space complexity O(n), displays the space overhead of the stack
// The traversal sequence of the preamble: middle left right, and the stacking sequence: middle right left
class Solution {
  
  public List<Integer> preorderTraversal(TreeNode root) {
      List<Integer> result = new LinkedList<>();
      Stack<TreeNode> st = new Stack<>();
      if (root != null) st.push(root);
      while (!st.empty()) {
          TreeNode node = st.peek();
          if (node != null) {
              st.pop();
              if (node.right!=null) st.push(node.right);
              if (node.left!=null) st.push(node.left);
              st.push(node);
              st.push(null);
              
          } else {
              st.pop();
              node = st.peek();
              st.pop();
              result.add(node.val);
          }
      }
      return result;
  }
}

// Middle order traversal order: left middle right stack order: left right
class Solution {
  public List<Integer> inorderTraversal(TreeNode root) {
  	    List<Integer> result = new LinkedList<>();
      Stack<TreeNode> st = new Stack<>();
      if (root != null) st.push(root);
      while (!st.empty()) {
          TreeNode node = st.peek();
          if (node != null) {
              st.pop();
              if (node.right!=null) st.push(node.right);
              st.push(node);
              st.push(null);

              if (node.left!=null) st.push(node.left);
          } else {
              st.pop(); 
              node = st.peek();
              st.pop();
              result.add(node.val);
          }
      }
      return result;
  }
}


// Post order traversal sequence: left - right - middle stack order: Middle - left - right stack order: Middle - right - left, and finally flip the result
class Solution {

 public List<Integer> postorderTraversal(TreeNode root) {
      List<Integer> result = new LinkedList<>();
      Stack<TreeNode> st = new Stack<>();
      if (root != null) st.push(root);
      while (!st.empty()) {
          TreeNode node = st.peek();
          if (node != null) {
              st.pop();
              st.push(node);
              st.push(null);
              if (node.right!=null) st.push(node.right);
              if (node.left!=null) st.push(node.left);    
                             
          } else {
              st.pop();
              node = st.peek();  
              st.pop();
              result.add(node.val); 
          }
      }
      return result;
 }
}

98. Validate binary search tree (medium)

Method 1. Recursion

  • Idea: using the nature of binary search tree, each node is larger than all nodes in its left subtree and smaller than all nodes in its right subtree, and the left and right subtrees of each node are not empty, then its left and right subtrees are also binary search trees. We can recursively verify the left and right subtrees of each node.
  • Complexity analysis: time complexity: O(n), n is a tree of binary tree nodes. Spatial complexity: O(n), n is the number of recursive layers. In the worst case, the binary tree is a chain, and the tree height is n, that is, a total of N layers

Js:

const helper = (root, lower, upper) => {
    if (root === null) {
        return true;
    }
    if (root.val <= lower || root.val >= upper) {//The termination condition does not satisfy the properties of binary search tree
        return false;
    }
  	//Recursive left and right subtree
    return helper(root.left, lower, root.val) && helper(root.right, root.val, upper);
}
var isValidBST = function(root) {
  	//Define low=-Infinity so that all values are greater than low
  	//Define upper=Infinity so that all values are less than upper
    return helper(root, -Infinity, Infinity);
};

Java:

class Solution {
    public boolean isValidBST(TreeNode root) {
        return helper(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }

    public boolean helper(TreeNode root, long lower, long upper) {
        if (root == null) {
            return true;
        }
        if (root.val <= lower || root.val >= upper) {
            return false;
        }
        return helper(root.left, lower, root.val) && helper(root.right, root.val, upper);
    }
}

Method 2: middle order traversal

The animation is too large. Click to view it

  • Idea: from the nature of binary search prime tree, if you traverse the binary search tree in middle order, you will get an ascending array
  • Complexity analysis: time complexity: O(n), each element is accessed once. Space complexity: O(n). The medium order traversal uses the stack to store elements, which requires additional O(n) space

Js:

//Non recursive version of middle order traversal
var isValidBST = function(root) {
    let stack = [];
    let inorder = -Infinity;

    while (stack.length || root !== null) {
        while (root !== null) {
            stack.push(root);
            root = root.left;
        }
        root = stack.pop();
        if (root.val <= inorder) {
            return false;
        }
        inorder = root.val;
        root = root.right;
    }
    return true;
};

//Recursive version of middle order traversal
var isValidBST = function (root) {
    let arr = [];
    const buildArr = (root) => {
        if (root) {
            buildArr(root.left);
            arr.push(root.val);
            buildArr(root.right);
        }
    }
    buildArr(root);
    for (let i = 1; i < arr.length; ++i) {
        if (arr[i] <= arr[i - 1])
            return false;
    }
    return true;
};

Java:

class Solution {
    public boolean isValidBST(TreeNode root) {
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        double inorder = -Double.MAX_VALUE;

        while (!stack.isEmpty() || root != null) {
            while (root != null) {
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();
            if (root.val <= inorder) {
                return false;
            }
            inorder = root.val;
            root = root.right;
        }
        return true;
    }
}

236. Nearest common ancestor of binary tree (medium)

Method 1. Recursion

  • Idea: there are four situations: 1.root is null or root is equal to p or q, indicating that root is the common ancestor of p and q. 2. Recurse the left and right subtrees. If the left and right subtree recursive functions do not return null, root is the common ancestor of p and q. 3. If the left subtree recursive function returns null, p and q are in the right subtree. 4. If the right subtree recursive function returns null, p, q is in the left subtree
  • Complexity analysis: time complexity: O(n), n is a tree of binary tree nodes, space complexity: O(n), n is a recursive layer

Js:

var lowestCommonAncestor = function(root, p, q) {
    // 1. Determine recursive functions
    const travelTree = function(root,p,q) {
        // 2. Determine recursive termination conditions
        if(root === null || root === p||root === q) {
            return root;
        }
        // 3. Determine recursive single layer logic
        let left = travelTree(root.left,p,q);
        let right = travelTree(root.right,p,q);
      	//If p and q can be found in both the left and right subtrees of a node, it means that the node is a common ancestor
        if(left !== null&&right !== null) {
            return root;
        }
        if(left ===null) {//If the left subtree is not found, it means that p and q are all in the right subtree
            return right;
        }
        return left;//If the right subtree is not found, it means that p and q are all in the left subtree
    }
   return  travelTree(root,p,q);//Recursive start
};

Java:

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        return travelTree(root, p, q);
    }
    public TreeNode travelTree(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) {
            return root;
        }
        TreeNode left = travelTree(root.left, p, q);
        TreeNode right = travelTree(root.right, p, q);
        if (left != null && right != null) {// The left and right subtrees are found respectively, indicating that the root at this time is the required result
            return root;
        }
        if (left == null) {
            return right;
        }
        return left;
    }
}

235. Nearest common ancestor of binary search tree (easy)

**Method 1: * * find the path of p or q from the left and right subtrees of the binary search tree, and finally find the first same node of the two paths, time complexity O(n), space complexity O(n)

Method 2: recursion

  • Idea: there are three cases: 1. The root node is greater than p and greater than q, indicating that both p and q are in the left subtree of root; 2. The root node is less than p and less than q, indicating that both p and q are in the right subtree of root; 3. Other cases, such as root is equal to p or q, indicating that root is the common ancestor. In the first two cases, it directly recurses the left and right subtrees, and in the third case, it directly returns root
  • Complexity analysis: time complexity: O(n). N is a tree of binary tree nodes. Space complexity: O(n). N is the number of recursive layers. In the worst case, it is equal to N. at this time, the tree is a linked list

Js:

var lowestCommonAncestor = function(root, p, q) {
    if(root === null) {//Recursive termination condition
        return root;
    }
  	//If the root node is greater than p and greater than q, both p and q are in the left subtree of root
    if(root.val>p.val&&root.val>q.val) {
        let left = lowestCommonAncestor(root.left,p,q);
        return left !== null&&left;
    }
  	//If the root node is less than p and less than q, both p and q are in the right subtree of root
    if(root.val<p.val&&root.val<q.val) {
        let right = lowestCommonAncestor(root.right,p,q);
        return right !== null&&right;
    }
    return root;//If none of the above conditions are met, root is a common ancestor
};

Java:

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
        if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
        return root;
    }
}

Method 3: iteration

  • Idea: it's the same as recursion, except that the root variable is used to iterate over the left and right subtrees
  • Complexity analysis: time complexity: O(n), space complexity: O(1), n is a tree of binary tree nodes

js:

var lowestCommonAncestor = function(root, p, q) {
    while(root) {
        if(root.val>p.val&&root.val>q.val) {
            root = root.left;
        }else if(root.val<p.val&&root.val<q.val) {
            root = root.right;
        }else {
            return root;
        }
        
    }
    return null;
};

java:

//java
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while (true) {
            if (root.val > p.val && root.val > q.val) {
                root = root.left;
            } else if (root.val < p.val && root.val < q.val) {
                root = root.right;
            } else {
                break;
            }
        }
        return root;
    }
}

102. Sequence traversal of binary tree (medium)

Method 1. Breadth first traversal

The animation is too large. Click to view it

  • Idea: prepare a queue, add the root node to the queue, cycle the queue when the queue is not empty, get the size of the current queue each cycle, cycle each element of the current layer, and then add it to the output array ret. if there are left and right nodes in this element, add the left and right nodes to the queue
  • Complexity analysis: the time complexity is O(n), and each point enters and exits the team once, so the progressive time complexity is O(n). The space complexity is O(n), and the number of elements in the queue does not exceed n

Js:

var levelOrder = function(root) {
    const ret = [];
    if (!root) {
        return ret;
    }

    const q = [];
    q.push(root);//Initial queue
    while (q.length !== 0) {
        const currentLevelSize = q.length;//Number of nodes in the current layer
        ret.push([]);//New layer push array
        for (let i = 1; i <= currentLevelSize; ++i) {//Loops the nodes of the current layer
            const node = q.shift();
            ret[ret.length - 1].push(node.val);//Push array of current layer
            if (node.left) q.push(node.left);//Check the left node. If there is a left node, continue to join the queue
            if (node.right) q.push(node.right);//Check the left and right nodes. If there is a right node, continue to join the queue
        }
    }
        
    return ret;
};

Java:

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ret = new ArrayList<List<Integer>>();
        if (root == null) {
            return ret;
        }

        Queue<TreeNode> q = new LinkedList<TreeNode>();
        q.offer(root);
        while (!q.isEmpty()) {
            List<Integer> level = new ArrayList<Integer>();
            int currentLevelSize = q.size();
            for (int i = 1; i <= currentLevelSize; ++i) {
                TreeNode node = q.poll();
                level.add(node.val);
                if (node.left != null) {
                    q.offer(node.left);
                }
                if (node.right != null) {
                    q.offer(node.right);
                }
            }
            ret.add(level);
        }
        
        return ret;
    }
}
Method 2: depth first traversal

The animation is too large. Click to view it

  • Idea: start from the root node, recurse the left and right subtrees continuously, and pass through the step layers and res output array.
  • Complexity analysis: time complexity O(n),n is the number of nodes. Spatial complexity O(n),n is the height of the tree.

js:

var levelOrder = function(root) {
    if(!root) return []
    let res = []
    dfs(root, 0, res)
    return res
};

function dfs(root, step, res){//Each layer transparently transmits the current node, number of layers, and output array
  if(root){
    if(!res[step]) res[step] = []//Initializes the current layer array
    res[step].push(root.val)//Add the current node to the current layer array
    dfs(root.left, step + 1, res)//step+1, recursive left node	
    dfs(root.right, step + 1, res)//step+1, recursive right node	
  }
}

Java:

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res  = new ArrayList<>();
        if(root != null){
            dfs(res, root, 0);
        }
        return res;
    }
    
    private void dfs(List<List<Integer>> res, TreeNode node, int step){
        if(res.size() - 1 < step){
            res.add(new ArrayList<Integer>());
        }
        res.get(step).add(node.val);
        if(node.left!=null){
            dfs(res, node.left, step + 1);
        }
        if(node.right!=null){
            dfs(res, node.right, step + 1);
        }
    }
}

107. Sequence traversal of binary tree II (medium)

Time complexity O(n), space complexity O(n)

js:

const levelOrderBottom = (root) => {
    if (root == null) {
        return [];
    }
    const queue = [];
    queue.push(root);
    const res = [];

    while (queue.length) {
        const subRes = [];
        const levelSize = queue.length;
        for (let i = 0; i < levelSize; i++) {
            const cur = queue.shift();
            subRes.push(cur.val);
            if (cur.left) {
                queue.push(cur.left);
            }
            if (cur.right) {
                queue.push(cur.right);
            }
        }
        res.unshift(subRes);//Different from 102, the direction of pushing into res is exactly the opposite
    }
    return res;
}



java:

class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> res = new LinkedList<List<Integer>>();
        if (root == null) {
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            List<Integer> subRes = new ArrayList<Integer>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                subRes.add(cur.val);
                TreeNode left = cur.left, right = cur.right;
                if (left != null) {
                    queue.offer(left);
                }
                if (right != null) {
                    queue.offer(right);
                }
            }
            res.add(0, subRes);
        }
        return res;
    }
}

104. Maximum depth of binary tree (easy)

Method 1.dfs

The animation is too large. Click to view it

  • Idea: the depth of a node is equal to 1 plus the greater of the left node and the node depth. The formula is h(r)=Math.max(h(r.left), h(right)) + 1, so you can traverse the left and right subtrees in depth and return the maximum depth of the left and right subtrees.
  • Complexity analysis: time complexity O(n), where n is the number of binary tree nodes. Spatial complexity O(n), where n represents the height of the binary tree. In the worst case, it is the same as the number of nodes of the tree

js:

var maxDepth = function(root) {
    if(!root) {
        return 0;
    } else {
        const left = maxDepth(root.left);//Recursive left subtree
        const right = maxDepth(root.right);//Recursive right subtree
        return Math.max(left, right) + 1;//1 plus the greater of the left node and the node depth
    }
};

Java:

class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) {
            return 0;
        } else {
            int left = maxDepth(root.left);
            int right = maxDepth(root.right);
            return Math.max(left, right) + 1;
        }
    }
}

Method 2.bfs

The animation is too large. Click to view it

  • Idea: the sequence traverses the binary tree, and at the end of each layer, depth is added by 1
  • Complexity analysis: time complexity O(n), n is the number of nodes of the binary tree, and each node will be accessed only once. The spatial complexity O(n) represents the number of elements stored in the queue. In the worst case, it is the same as the number of binary tree nodes

Js:

const maxDepth = (root) => {
    if (root == null) return 0;
    const queue = [root];
    let depth = 1;
    while (queue.length) {
        // Number of nodes in the current layer
        const levelSize = queue.length;          
        // List the nodes of the current layer one by one
        for (let i = 0; i < levelSize; i++) {    
            // Currently dequeued node
            const cur = queue.shift();            
            // The left and right child nodes are listed
            if (cur.left) queue.push(cur.left);
            if (cur.right) queue.push(cur.right); 
        }
        // All nodes in the current layer have been listed. If the queue is not empty, it indicates that there are nodes in the next layer, depth+1
        if (queue.length) depth++;
    }
    return depth;
};

Java:

class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int depth = 0;
        while(!queue.isEmpty()){
            int levelSize = queue.size();
            for(int i = 0; i < levelSize; i++){
                TreeNode node = queue.poll();
                if(node.left != null){
                    queue.offer(node.left);
                }
                if(node.right != null){
                    queue.offer(node.right);
                }
            }
            depth++;
        }
        return depth;
    }
}

111. Minimum depth of binary tree (easy)

Method 1.dfs

  • Idea: depth first traverses the left and right subtrees and decomposes them into sub problems. The minimum depth of the tree is equal to the minimum depth of the left and right subtrees + 1
  • Complexity analysis: time complexity O(n), where n is the number of binary tree nodes. Spatial complexity O(n), where n represents the height of the binary tree. In the worst case, the tree is chain like and has the same number of nodes as the tree. On average, the height of the tree is positively correlated with the logarithm of the number of nodes, and the spatial complexity is O(logn)

js:

var minDepth = function(root) {
    if(root == null) {
        return 0;
    }
    if(root.left == null && root.right == null) {//Traverse to leaf node termination
        return 1;
    }
    let ans = Number.MAX_SAFE_INTEGER;
    if(root.left != null) {
        ans = Math.min(minDepth(root.left), ans);//Recurse the left subtree to find the minimum depth of the left subtree
    }
    if(root.right != null) {
        ans = Math.min(minDepth(root.right), ans);//Recursive right subtree to find the minimum depth of right subtree
    }
    return ans + 1;//The minimum depth is equal to the minimum depth of left and right subtrees + 1
};

Java:

class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) {
            return 0;
        }
        if(root.left == null && root.right == null) {
            return 1;
        }
        int ans = Integer.MAX_VALUE;
        if(root.left != null) {
            ans = Math.min(minDepth(root.left), ans);
        }
        if(root.right != null) {
            ans = Math.min(minDepth(root.right), ans);
        }
        return ans + 1;
    }
}

Method 2.bfs

The animation is too large. Click to view it

  • Idea: breadth first traverses the binary tree. The depth of each layer is + 1. When the first leaf node terminates, the depth at this time is the minimum depth
  • Complexity analysis: time complexity O(n), n is the number of nodes of the binary tree, and each node will be accessed only once. The spatial complexity O(n) represents the number of elements stored in the queue. In the worst case, it is the same as the number of binary tree nodes

js:

var minDepth = function(root) {
    if(root == null) return 0;
    let q = [root];
    //root itself is a layer, so the depth is initialized to 1
    let depth = 1;
    while(q.length){
        let len = q.length;
        for(let i=0; i<len; i++){
            let curr = q.shift();
            //Determine whether the current node is a leaf node
            if(curr.left == null && curr.right == null){
                return depth;
            }
            //If it is not a leaf node, its adjacent nodes are added to the queue
            if(curr.left){
                q.push(curr.left);
            }
            if(curr.right){
                q.push(curr.right);
            }
        }
        depth++;
    }
    return depth;

};

Java:

class Solution {
    public int minDepth(TreeNode root) {
       if (root == null) {
           return 0;
       }
       int depth = 0;
       Queue<TreeNode> q = new LinkedList<>();
       q.offer(root);

       while (!q.isEmpty()) {
           int sz = q.size();
           depth++;
           for (int i = 0; i < sz; i++) {
               TreeNode cur = q.poll();
               if (cur.left == null && cur.right == null) {
                   return depth;
               }
               if (cur.left != null) {
                   q.offer(cur.left);
               }
               if (cur.right != null) {
                   q.offer(cur.right);
               }
           }
       }
       return depth;
    }
}

127. Word Solitaire (hard)

  • Idea: bfs, loop beginWord, replace 26 lowercase characters for each character, and see whether the newly generated word is in the wordList. If it exists, it is a legal path. Until the newly generated word is endWord, the loop returns 0 when it is completed or does not reach endWord. The idea of two-way bfs is the same, but it is to move closer from both sides to the middle to judge whether the newly generated word is in the path in the other direction.
Method 1:bfs

js:

const ladderLength = (beginWord, endWord, wordList) => {
  const wordSet = new Set(wordList);
  const queue = [];
  queue.push([beginWord, 1]);//Start adding words and levels to the queue

  while (queue.length) {
    const [word, level] = queue.shift()//Team out for bfs
    if (word == endWord) {//Return level equal to endword
      return level;
    }
    for (let i = 0; i < word.length; i++) { //Circular word list
      for (let c = 97; c <= 122; c++) { //Loop 26 lowercase characters
        //Get new words
        const newWord = word.slice(0, i) + String.fromCharCode(c) + word.slice(i + 1);
        if (wordSet.has(newWord)) { //Check that the wordset package does not include the newly generated words to avoid repetition
          queue.push([newWord, level + 1]); //Add new words to the queue
          wordSet.delete(newWord);  //Avoid dead cycle
        }
      }
    }
  }
  return 0; // bfs ends and never meets the end
};
Method 2: bidirectional bfs

js:

var ladderLength = function(beginWord, endWord, wordList) {
    let wordSet = new Set(wordList)
    if (!wordSet.has(endWord)) return 0
    //From beginWord to endWord
    let begSet = []
		//From endWord to beginWord
    let endSet = []
    begSet.push(beginWord)
    endSet.push(endWord)

    let n = 1//Hierarchy
    while(begSet.length > 0){//Start traversing words
        const nextBegins = []//bfs next level word array
        //Change a few steps to make the slow one take a few steps
        if(begSet.length > endSet.length){
            let q = begSet
            begSet = endSet
            endSet = q
        }
        //Loop begSet
        for(let k = 0; k < begSet.length;k++){
            let m = begSet[k]
            for(let i = 0; i < m.length; i++){
                for (let c = 97; c <= 122; c++){
                    //Generate new words
                    let newm = m.slice(0, i) + String.fromCharCode(c) + m.slice(i + 1);
                    if(endSet.includes(newm)){//meet
                        return n + 1
                    }
                    if( wordSet.has(newm)){
                        nextBegins.push(newm)	//Word array of bfs in the next layer
                        wordSet.delete(newm)  //Prevent duplication
                    } 
                }
            }
        }
        begSet = nextBegins
        n++ //Level + 1
    }
    return 0
   
};

297. Serialization and deserialization of binary tree (hard)

Method 1. Recursive dfs
  • Idea: depth first traversal, return strings by root, left and right, facilitate the construction from the root node during deserialization, and recurse the left and right subtrees until a null node is encountered.
  • Complexity: time complexity O(n). Each node is accessed once. N is the number of nodes in the tree. The space complexity is O(n). In the worst case, the recursion depth is n

js:

const serialize = (root) => {
    if (root == null) {                  //If null is encountered, return 'X' for marking
        return 'X';
    }
    const left = serialize(root.left);   //Serialize left subtree
    const right = serialize(root.right); //Serialized right subtree
    return root.val + ',' + left + ',' + right; //Return string by root, left and right
};

const deserialize = (data) => {
    const list = data.split(',');   //String to array

    const buildTree = (list) => {  	//Build tree
        const rootVal = list.shift(); //First element
        if (rootVal == "X") {         //null if X
            return null;
        }
        const root = new TreeNode(rootVal); //If it is not X, create a node
        root.left = buildTree(list);        //Build left subtree
        root.right = buildTree(list);       //Constructing right subtree
        return root;                        //Returns the built node
    };

    return buildTree(list);
};


Method 2:bfs

The animation is too large. Click to view it

  • Idea: breadth first traverse the nodes, keep the child nodes in the queue, and serialize and deserialize according to the order around the root
  • Complexity: time complexity O(n). Each node is accessed once. N is the number of nodes in the tree. Space complexity O(n), space of queue

js:

const serialize = (root) => {
    const queue = [root];
    let res = [];
    while (queue.length) {
        const node = queue.shift(); //Out of the team
        if (node) {                 //node exists around the root
            res.push(node.val);
            queue.push(node.left);
            queue.push(node.right);
        } else {                    //If 'x' is not present
            res.push('X');
        }
    }
    return res.join(',');  //Convert array to string
}

const deserialize = (data) => {
    if (data == 'X') return null;

    const list = data.split(',');  //String to array

    const root = new TreeNode(list[0]); //Build from team leader
    const queue = [root];          //The root node joins the queue
    let cursor = 1;                //How many nodes have you traversed

    while (cursor < list.length) { //When the queue is not traversed
        const node = queue.shift();  //Out of the team

        const leftVal = list[cursor];      //Left node value
        const rightVal = list[cursor + 1]; //Value of the right node

        if (leftVal != 'X') {              //Is not an empty node
            const leftNode = new TreeNode(leftVal); //Build left node
            node.left = leftNode;                   //The left node is hung under the left of the parent node
            queue.push(leftNode);                   //List yourself and build a subtree with your own root
        }
        if (rightVal != 'X') {
            const rightNode = new TreeNode(rightVal);
            node.right = rightNode;
            queue.push(rightNode);
        }
        cursor += 2; //Number of nodes built + 2
    }
    return root;  //Return root
};

226. Flip binary tree (easy)

Method 1: recursion
  • Idea: recurse the left and right subtrees and reverse the left and right nodes
  • Complexity: the time complexity is O(n), and each node of the tree will be traversed. Space complexity O(n), recursive stack space, in the worst case, is the same as the number of nodes n

js:

var invertTree = function(root) {
    if (root === null) {//Recursive termination condition
        return null;
    }
    const left = invertTree(root.left);//Recursive left subtree
    const right = invertTree(root.right);//Recursive right subtree
  	//Swap left and right nodes
    root.left = right;
    root.right = left;
    return root;
};

java:

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) {
            return null;
        }
        TreeNode left = invertTree(root.left);
        TreeNode right = invertTree(root.right);
        root.left = right;
        root.right = left;
        return root;
    }
}

100. Same tree(easy)

Method 1.dfs recursion
  • Idea: recursively compare left and right subtrees
  • Complexity: time complexity O(n). N is the number of nodes in a tree with fewer nodes. If one node is null and the other is not null, the recursion will stop. Space complexity O(n). The recursion depth will not exceed the number of nodes

js:

var isSameTree = function(p, q) {
    if(p == null && q == null) //null means the same
        return true;
    if(p == null || q == null) //Only one is null, which means different
        return false;
    if(p.val != q.val) //Nodes have different values
        return false;
    return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);//Recursive left and right subtree
};


java:

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null) 
            return true;
        if(p == null || q == null) 
            return false;
        if(p.val != q.val) 
            return false;
        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
}

Method 2bfs:
  • Complexity: time complexity O(n), n is the number of nodes in the tree with fewer nodes, space complexity O(n), and the space of the queue will not exceed the number of nodes in the tree with fewer nodes

js:

var isSameTree = function(p, q) {
    let pQ = [p], qQ = [q], res = true

    while (pQ.length) {
        pNode = pQ.shift()
        qNode = qQ.shift()

        if (pNode === null && qNode === null) {
            res = true
        } else if (pNode === null || qNode === null) {
            res = false
            break 
        } else {
            if (pNode.val !== qNode.val) {
                res = false
                break 
            } else {
                pQ.push(pNode.left)
                pQ.push(pNode.right)

                qQ.push(qNode.left)
                qQ.push(qNode.right)
            }
        }
    }

    return res
};

java:

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        Queue<TreeNode> pQ = new LinkedList<TreeNode>();
        Queue<TreeNode> qQ = new LinkedList<TreeNode>();
        pQ.offer(p);
        qQ.offer(q);
        boolean res = true;

        while (!pQ.isEmpty()) {
            TreeNode pNode = pQ.poll();
            TreeNode qNode = qQ.poll();

            if (pNode == null && qNode == null) {
                res = true;
            } else if (pNode == null || qNode == null) {
                res = false;
                break ;
            } else {
                if (pNode.val != qNode.val) {
                    res = false;
                    break ;
                } else {
                    pQ.offer(pNode.left);
                    pQ.offer(pNode.right);

                    qQ.offer(qNode.left);
                    qQ.offer(qNode.right);
                }
            }
        }
        return res;
    }
}

101. Symmetric binary tree (easy)

Method 1.dfs recursion
  • Complexity: time complexity O(n), traversal of each node once, space complexity O(n), recursive stack depth, the deepest not exceeding n

js:

var isSymmetric = function(root) {
    if(!root) return true
    const isMirror = (l, r) => {
        if(!l && !r) return true; //Two empty nodes are also mirrored
        if(
            l && r && l.val === r.val &&  //If the left node and the right node are the same, and the left subtree and the right subtree are also symmetrical, true is returned
            isMirror(l.left, r.right) && 
            isMirror(l.right, r.left)
        ) {
            return true;
        }
        return false;
    }
    return isMirror(root.left, root.right)
};

java:

class Solution {
    public boolean isSymmetric(TreeNode root) {
        return isMirror(root, root);
    }

    public boolean isMirror(TreeNode l, TreeNode r) {
        if (l == null && r == null) {
            return true;
        }
        if (l == null || r == null) {
            return false;
        }
        return l.val == r.val && isMirror(l.left, r.right) && isMirror(l.right, r.left);
    }
}
Method 2.bfs
  • Complexity: time complexity O(n), traversal of each node, space complexity O(n), space of the queue

js:

function isSymmetric(root) {
    const isMirror = (l, r) => {
        const queue = [l, r];
        while (queue.length) {
            const u = queue.shift();
            const v = queue.shift();
            if (!u && !v) continue; //Two empty nodes are also mirrored
            //Only one of the left and right nodes is empty, or the values are different. false is returned
            if (!u || !v || u.val !== v.val) return false;
            queue.push(u.left, v.right); //Join the left subtree of the left node and the right subtree of the right node
            queue.push(v.left, u.right); //Join the left subtree of the right node and the right subtree of the left node
        }
        return true;
    };
    return isMirror(root.left, root.right);
}

java:

class Solution {
    public boolean isSymmetric(TreeNode root) {
        return isMirror(root, root);
    }

    public boolean isMirror(TreeNode u, TreeNode v) {
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(u);
        queue.offer(v);
        while (!queue.isEmpty()) {
            u = queue.poll();
            v = queue.poll();
            if (u == null && v == null) {
                continue;
            }
            if ((u == null || v == null) || (u.val != v.val)) {
                return false;
            }

            queue.offer(u.left);
            queue.offer(v.right);

            queue.offer(u.right);
            queue.offer(v.left);
        }
        return true;
    }
}

112. Path sum (easy)

  • Idea: recurse the left and right subtrees and constantly let sum subtract the value of the current node. If one of the left and right subtrees returns true, such a path is found
  • Complexity: time complexity O(n), n is the number of nodes, and each node is traversed once. The space complexity O(n), which depends on the recursive stack space, is O(n) in the worst case

js:

const hasPathSum = (root, sum) => {
    if (root == null) {//Recursive termination condition
        return false;
    }
    if (root.left == null && root.right == null) {//Traverse to leaf node
        return sum - root.val == 0;                 //sum is exactly reduced to 0, return true, otherwise return false
    }
    //Recursive left and right subtrees, one of which returns true to find such a path
    return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}

java:

class Solution {
    public boolean hasPathSum(TreeNode root, int sum) {
        if (root == null) {
            return false;
        }
        if (root.left == null && root.right == null) {
            return sum == root.val;
        }
        return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
    }
}

437. Path sum III (medium)

Method 1:dfs
  • Idea: recurse the left and right subtrees. In the recursive sub stage, continue to find the path with this node as the root node
  • Complexity: the time complexity is O(n^2). All nodes must traverse one side and find out whether there are qualified paths in the subtree with this node as the root. Space complexity O(n), recursive space

js:

var pathSum = function(root, targetSum) {
    if (root == null) {
        return 0;
    }
    
    let ret = rootSum(root, targetSum);//Find the path in the tree with root as the root node
    ret += pathSum(root.left, targetSum);//Recursive left subtree to find the path in the left subtree
    ret += pathSum(root.right, targetSum);//Recursive right subtree to find the path in the left subtree
    return ret;
};

const rootSum = (root, targetSum) => {
    let ret = 0;

    if (root == null) {
        return 0;
    }
    const val = root.val;
    if (val === targetSum) {
        ret++;
    } 

  	//Take root.left as the root node and targetSum - val as the search path and continue to search for the path
    ret += rootSum(root.left, targetSum - val);
  	//Take root.right as the root node and targetSum - val as the search path and continue to search for the path
    ret += rootSum(root.right, targetSum - val);
    return ret;
}

java:

class Solution {
    public int pathSum(TreeNode root, int targetSum) {
        if (root == null) {
            return 0;
        }

        int ret = rootSum(root, targetSum);
        ret += pathSum(root.left, targetSum);
        ret += pathSum(root.right, targetSum);
        return ret;
    }

    public int rootSum(TreeNode root, int targetSum) {
        int ret = 0;

        if (root == null) {
            return 0;
        }
        int val = root.val;
        if (val == targetSum) {
            ret++;
        } 

        ret += rootSum(root.left, targetSum - val);
        ret += rootSum(root.right, targetSum - val);
        return ret;
    }
}

Method 2: prefix and

  • Idea: record the path and curr from the root node to the node. Recurse the left and right subtrees to see if there is a path with curr - targetSum as the path sum in the previously traversed path. If so, subtracting this path from the path from the root node to the current node is one of the qualified paths. See the figure
  • Complexity: the time complexity is O(n), and the nodes in the binary tree are traversed only once. Space complexity O(n), recursive stack depth.

js:

var pathSum = function(root, targetSum) {
    const prefix = new Map();//Store prefix and its number of paths
    prefix.set(0, 1);//The number of initialization prefixes and is 0
    return dfs(root, prefix, 0, targetSum);
}

const dfs = (root, prefix, curr, targetSum) => {//curr refers to the sum on the path to the current node
    if (root == null) {//An empty node has no path and returns 0
        return 0;
    }

    let ret = 0;
    curr += root.val;//Add current node

    ret = prefix.get(curr - targetSum) || 0;//Gets the number of prefixes and with curr - targetSum
    prefix.set(curr, (prefix.get(curr) || 0) + 1);//Add the prefix and number of curl in prefix
    ret += dfs(root.left, prefix, curr, targetSum);//Recursive left subtree plus left subtree prefix and the number of paths for targetSum
    ret += dfs(root.right, prefix, curr, targetSum);//Recursive right subtree plus right subtree prefix and the number of paths for targetSum
    prefix.set(curr, (prefix.get(curr) || 0) - 1);//After node recursion, backtrack the prefix and quantity of curr to avoid double calculation

    return ret;
}

java:

class Solution {
    public int pathSum(TreeNode root, int targetSum) {
        HashMap<Long, Integer> prefix = new HashMap<>();
        prefix.put(0L, 1);
        return dfs(root, prefix, 0, targetSum);
    }

    public int dfs(TreeNode root, Map<Long, Integer> prefix, long curr, int targetSum) {
        if (root == null) {
            return 0;
        }

        int ret = 0;
        curr += root.val;

        ret = prefix.getOrDefault(curr - targetSum, 0);
        prefix.put(curr, prefix.getOrDefault(curr, 0) + 1);
        ret += dfs(root.left, prefix, curr, targetSum);
        ret += dfs(root.right, prefix, curr, targetSum);
        prefix.put(curr, prefix.getOrDefault(curr, 0) - 1);

        return ret;
    }
}

257. All paths of binary tree (easy)

Method 1:dfs
  • Idea: recurse the left and right subtrees until the leaf node. In the process of recursion, the path is continuously transmitted. Each layer of recursion is connected to the value of the current node
  • Complexity: the time complexity is O(n), and each node is traversed once. Space complexity O(n), recursive stack space

js:

var binaryTreePaths = function(root) {
    const paths = [];
    const dfs = (root, path) => {//Pass in a recursive array of nodes and paths
        if (root) {
            path += root.val.toString();//Join current node
          	//The leaf node adds all connected node strings to paths, which is one of the paths
            if (root.left === null && root.right === null) { 
                paths.push(path); 
            } else {
                path += "->"; //Not leaf nodes continue to recurse left and right subtrees
                dfs(root.left, path);
                dfs(root.right, path);
            }
        }
    }
    dfs(root, "");
    return paths;
};

java:

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> paths = new ArrayList<String>();
        dfs(root, "", paths);
        return paths;
    }

    public void dfs(TreeNode root, String path, List<String> paths) {
        if (root != null) {
            StringBuffer path1 = new StringBuffer(path);
            path1.append(Integer.toString(root.val));
            if (root.left == null && root.right == null) {
                paths.add(path1.toString());
            } else {
                path1.append("->");
                dfs(root.left, path1.toString(), paths);
                dfs(root.right, path1.toString(), paths);
            }
        }
    }
}
Method 2:bfs

The animation is too large. Click to view it

  • Idea: use queue to assist in breadth first traversal, and constantly add the left and right child nodes to the queue until the leaf node
  • Complexity: the same as method 1

js:

var binaryTreePaths = function(root) {
    const res = [];
    if (root === null) {
        return res;
    }
    const nodes = [root];
    const paths = [root.val.toString()];

    while (nodes.length) {
        const node = nodes.shift(); 
        const path = paths.shift();

        if (node.left === null && node.right === null) {//Leaf node
            res.push(path);
        } else {
            if (node.left !== null) {//The left node is not empty and joins the queue
                nodes.push(node.left);
                paths.push(path + "->" + node.left.val.toString());
            }

            if (node.right !== null) {//The right node is not empty and joins the queue
                nodes.push(node.right);
                paths.push(path + "->" + node.right.val.toString());
            }
        }
    }
    return res;
};


java:

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new ArrayList<String>();
        if (root == null) {
            return res;
        }
        Queue<TreeNode> nodeQueue = new LinkedList<TreeNode>();
        Queue<String> pathQueue = new LinkedList<String>();

        nodeQueue.offer(root);
        pathQueue.offer(Integer.toString(root.val));

        while (!nodeQueue.isEmpty()) {
            TreeNode node = nodeQueue.poll(); 
            String path = pathQueue.poll();

            if (node.left == null && node.right == null) {
                res.add(path);
            } else {
                if (node.left != null) {
                    nodeQueue.offer(node.left);
                    pathQueue.offer(new StringBuffer(path).append("->").append(node.left.val).toString());
                }

                if (node.right != null) {
                    nodeQueue.offer(node.right);
                    pathQueue.offer(new StringBuffer(path).append("->").append(node.right.val).toString());
                }
            }
        }
        return res;
    }
}

199. Right view of binary tree (medium)

Method 1:dfs
  • Idea: depth first traversal. First recurse the right node and let it be processed at the next layer. When the length of res is equal to step, the current node is the right node of this layer and added to the array
  • Complexity: the time complexity is O(n), and each node is traversed once. Space complexity O(n), recursive stack space

js:

var rightSideView = function (root) {
    function dfs(root, step, res) {//Pass in the number of root node layers and the array of right looking nodes
        if (root) {
            if (res.length === step) {
                res.push(root.val); //When the length of res and step are equal, the current node is the right node of this layer and added to the array
            }
            dfs(root.right, step + 1, res); //Recurse the right node first and let it be processed first at the next layer
            dfs(root.left, step + 1, res);
        }
    }
    if (!root) return [];
    let arr = [];
    dfs(root, 0, arr);
    return arr;
};

java:

class Solution {
    List<Integer> res;
    public List<Integer> rightSideView(TreeNode root) {
        res = new LinkedList<>();
        if (root == null) {
            return res;
        }
        dfs(root, 0);
        return res;
    }

    private void dfs(TreeNode root, int depth) {
        if (root == null) {
            return;
        }
        if (depth == res.size()) {
            res.add(root.val);
        }
        dfs(root.right, depth+1);
        dfs(root.left, depth+1);
    }
}

Method 2:bfs
  • Idea: breadth first traversal, record the number of nodes in each layer, and reduce the length by 1 after leaving the team. When the length of the previous layer is equal to 1, it indicates that it is the edge node.
  • Complexity: the time complexity is O(n), and each node is traversed once. Space complexity O(n), space of queue

js:

var rightSideView = function (root) {
    if (!root) return [];
    let queue = [root]; //The breadth first traversal queue is first added to root
    let arr = []; //Store right looking nodes
    while (queue.length > 0) {
        let len = queue.length;
        while (len) {
            let node = queue.shift(); //Fetch the first element of the queue
            if (len === 1) arr.push(node.val); //When the length of the previous layer is equal to 1, it indicates that it is the outermost node
            if (node.left) queue.push(node.left); //Continue adding left and right nodes to the queue
            if (node.right) queue.push(node.right);
            len--;//After leaving the team, the length of the current layer is reduced by 1
        }
    }
    return arr;
};

java:

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> res = new LinkedList<>();
        if (root == null) {
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            TreeNode curr = null;
            for (int i = 0; i < size; ++i) {
                curr = queue.poll();
                if (curr.left != null) {
                    queue.offer(curr.left);
                }
                if (curr.right != null) {
                    queue.offer(curr.right);
                }
            }
            res.add(curr.val);//Or in another way of thinking, after the current layer is cycled, the last one out of the team is the rightmost one of the current layer
        }
        return res;
    }
}

Keywords: Algorithm leetcode Interview

Added by leewad on Wed, 08 Dec 2021 00:32:52 +0200