Trees

Trees

(Solutions can be found in my github repo )

A tree data structure can be defined recursively as a collection of nodes, where each node is a data structure consisting of a value and a list of references to nodes.

Trees should not have cycles

file

A straightforward class definition for Node:

class TreeNode(_value: Int = 0, _left: TreeNode = null, _right: TreeNode = null) {  
  var value: Int = _value  
  var left: TreeNode = _left  // Leaf: left would be null
  var right: TreeNode = _right // Leaf: right would be null
}

Example tree


  val three = TreeNode(3, null, null)  
  val two = TreeNode(2, three, null)  
  val one = TreeNode(1, null, two)  // the root

Real World Examples

  1. Most XML/Markup parsers use trees. See Apache Xerces for example. Or, the Xalan XSLT parser.

Basic XML structure

  1. PDF is a tree based format. It has a root node followed by a catalog node(these are often the same) followed by a pages node which has several child page nodes. Producers/consumers often use a balanced tree implementation to store a document in memory.

PDF Theory of Operation | Skia

  1. Decision Tree based Learning actually forms a formidable area of data mining research. Numerous famous methods exist like bagging, boosting, and modifications thereof which work on trees. Such work is often used to generate a predictive model.

    Decision Trees - Predict by Asking Questions | Data Science Blog

Terminology

8 Useful Tree Data Structures Worth Knowing | by Vijini Mallawaarachchi |  Towards Data Science

Root
The start of the tree is the "root node."

file

Children
Reference nodes are the "children."

file

Siblings
In a tree data structure, nodes which belong to same Parent are called as SIBLINGS. In simple words, the nodes with the same parent are called Sibling nodes.

file

Parent
In a tree data structure, the node which is a predecessor of any node is called as PARENT NODE. In simple words, the node which has a branch from it to any other node is called a parent node. Parent node can also be defined as "The node which has child / children".

Neighbour
Parent or child.

Ancestor
A node is reachable by repeated proceeding from child to parent.

Descendant / Subchild
A node that is reachable by repeated proceeding from parent to child. Also known as sub child.

Internal Nodes
In a tree data structure, the node which has atleast one child is called as INTERNAL Node. In simple words, an internal node is a node with atleast one child.

In a tree data structure, nodes other than leaf nodes are called as Internal Nodes. The root node is also said to be Internal Node if the tree has more than one node. Internal nodes are also called as ‘Non-Terminal’ nodes.

file

Degree
For a given node, its number of children. A leaf has necessarily degree zero.

file

Degree of Tree
The degree of a tree is the maximum degree of a node in the tree.

Depth
In a tree data structure, the total number of egdes from root node to a particular node is called as DEPTH of that Node. In a tree, the total number of edges from root node to a leaf node in the longest path is said to be Depth of the tree. In simple words, the highest depth of any leaf node in a tree is said to be depth of that tree. In a tree, depth of the root node is ‘0’.

Edge

In a tree data structure, the connecting link between any two nodes is called as EDGE. In a tree with ‘N‘ number of nodes there will be a maximum of ‘N-1‘ number of edges.

file

Distance
"Distance" is the number of edges along the shortest path between two nodes.

Level
The level of a node is the number of edges along the unique path between it and the root node. In a tree data structure, the root node is said to be at Level 0 and the children of root node are at Level 1 and the children of the nodes which are at Level 1 will be at Level 2 and so on… In simple words, in a tree each step from top to bottom is called as a Level and the Level count starts with ‘0’ and incremented by one at each level (Step).

file

Width
The number of nodes in a level.

Height
In a tree data structure, the total number of edges from leaf node to a particular node in the longest path is called as HEIGHT of that Node. In a tree, height of the root node is said to be height of the tree. In a tree, height of all leaf nodes is ‘0’.

Breadth
The number of leaves.

Forest
A set of n ≥ 0 disjoint trees.

Sub Tree
In a tree data structure, each child from a node forms a subtree recursively. Every child node will form a subtree on its parent node.

file

Size of a Tree
The number of nodes in the tree.

Leaf
A node is called a "leaf" if it has no children

file

Different Types of Trees

Binary Tree

Each node has up to two children
Binary tree - Wikipedia

Ternary Tree

Each node has up to three children
Ternary tree - Wikipedia

Binary Search Tree

In computer science, a binary search tree (BST), also called an ordered or sorted binary tree, is a rooted binary tree data structure whose internal nodes each store a key greater than all the keys in the node’s left subtree and less than those in its right subtree.

i.e. For a node with value n :

all left descendents <= n < all right descendents

Note: All descents – not just its immediate children

Not a binary search tree: 12 > 8

file

Figure: Binary search tree

Usage

A binary tree is a type of data structure for storing data such as numbers in an organized way. Binary search trees allow binary search for fast lookup, addition and removal of data items and can be used to implement dynamic sets and lookup tables.

Time Complexity

Algorithm Average Worst case
Space O( n ) O( n )
Search O(log n) O( n )
Insert O(log n) O( n )
Delete O(log n) O( n )

Validate Binary Search Tree

https://leetcode.com/problems/validate-binary-search-tree/
https://leetcode-cn.com/problems/validate-binary-search-tree/

Given the root of a binary tree, determine if it is a valid binary search tree (BST).

A valid BST is defined as follows:

The left subtree of a node contains only nodes with keys less than the node’s key.
The right subtree of a node contains only nodes with keys greater than the node’s key.
Both the left and right subtrees must also be binary search trees.

Example 1:

Input: root = [2,1,3]
Output: true
Example 2:

Input: root = [5,1,4,null,null,3,6]
Output: false
Explanation: The root node’s value is five, but its right child’s value is 4.

Solution


object LeetCode_98_Validate_Binary_Search_Tree {  
  def isValidBST(root: TreeNode): Boolean = {  
  validate(root, Long.MinValue, Long.MaxValue)  
 }  
  def validate(node: TreeNode, min: Long, max: Long): Boolean = {  
  if (node == null) true else {  
  if (node.value > min && node.value < max) {  
  validate(node.left, min, node.value) && validate(node.right, node.value, max)  
 } else false  
  }  
 }}

Test

import org.scalatest.funsuite.AnyFunSuite  
import org.scalatest.matchers.should._  

class LeetCode_98_Validate_Binary_Search_TreeTest extends AnyFunSuite with Matchers {  
  val tree1 = "2,1,3"  
  val tree2 = "5,1,4,null,null,3,6"  

  test("This tree is a valid BST") {  
  isValidBST(deserialize(tree1)) shouldBe true  
  }  

  test("This tree is not a valid BST") {  
  isValidBST(deserialize(tree2)) shouldBe false  
  }  
}

Balanced / Unbalanced

A tree that is balanced enough to ensure O(log n) times for search/insert/delete

Does not mean the left and right subtree are the same sizes – that is the "Perfect binary tree."

Figure: Balanced vs Unbalanced
Balanced vs Unbalanced Binary Tree - Clarification Needed - Stack Overflow

Validation

https://leetcode-cn.com/problems/balanced-binary-tree/
https://leetcode.com/problems/balanced-binary-tree/

Given a binary tree, determine if it is height-balanced.

Example 1:

Input: root = [3,9,20,null,null,15,7]
Output: true
Example 2:

Input: root = [1,2,2,3,3,null,null,4,4]
Output: false
Example 3:

Input: root = []
Output: true

Solution: For this problem, a height-balanced binary tree is defined as a binary tree in which every node’s left and right subtrees differ in height by no more than 1. Recursively check all nodes.

Solution

import scala.math._  

object LeetCode_110_Balanced_Binary_Tree {  
  def getHeight(root: TreeNode): Int = {  
  if (root == null) 0 else {  
  max(getHeight(root.right), getHeight(root.left)) + 1  
  }  
 }  def isBalanced(root: TreeNode): Boolean = {  
  if (root == null) true else {  
  val rootBalanced: Boolean = abs(getHeight(root.right) - getHeight(root.left)) <= 1  
  if (!rootBalanced) {  
  println(root)  
 }  rootBalanced && isBalanced(root.left) && isBalanced(root.right)  
 } }}

Test

import org.scalatest.funsuite.AnyFunSuite  
import org.scalatest.matchers.should._  

class LeetCode_110_Balanced_Binary_TreeTest extends AnyFunSuite with Matchers {  
  val tree1 = "4,-7,-3,null,null,-9,-3,9,-7,-4,null,6,null,-6,-6,null,null,0,6,5,null,9,null,null,-1,-4,null,null,null,-2"  
  val tree2 = "1,2,3,null,null,4,5"  

  test("tree1 is not a balanced tree") {  
  assert(isBalanced(deserialize(tree1)) == false)  
 }  
  test("tree2 is a balanced tree") {  
  isBalanced(deserialize(tree2)) shouldBe true  
  }  

}

Red-Black Trees

A red-black tree is a kind of self-balancing binary search tree. Each node stores an extra bit representing “colour” (“red” or “black”), used to ensure that the tree remains balanced during insertions and deletions.

When the tree is modified, the new tree is rearranged and “repainted” to restore the colouring properties that constrain how unbalanced the tree can become in the worst case. The properties are designed such that this rearranging and recolouring can be performed efficiently.

Red-Black Tree | Set 1 (Introduction) - GeeksforGeeks

AVL Trees

An AVL tree (named after inventors Adelson-Velsky and Landis) is a self-balancing binary search tree. It was the first such data structure to be invented. In an AVL tree, the heights of the two-child subtrees of any node differ by at most one; if at any time they differ by more than one, rebalancing is done to restore this property. Lookup, insertion, and deletion take O(log n) time in both the average and worst cases, where n is the number of nodes in the tree prior to the operation. Insertions and deletions may require the tree to be rebalanced by one or more tree rotations.

Balanced Search Trees - AVL Tree - Techie Me

Complete Binary Tree

A complete binary tree is a binary tree in which all the levels are filled except possibly the lowest one, which is filled from the left.

Full Binary Tree

A full binary tree can be defined as a binary tree in which all the nodes have 0 or two children.

i.e. All the nodes have two children except the leaf nodes.

Binary Trees

Complete Binary Tree

Perfect Binary Tree

  • A perfect binary tree is a binary tree in which all interior nodes have two children and all leaves have the same depth or same level.

Provided the ancestry chart always displays the mother and the father on the same side for a given node, their sex can be seen as an analogy of left and right children, children being understood here as an algorithmic term. A perfect tree is therefore always complete, but a complete tree is not necessarily perfect.

Perfect Binary Tree

Symmetric Tree

Symmetric: it is a mirror of itself (i.e., symmetric around its centre).

Validate Symmetric Trees

https://leetcode.com/problems/symmetric-tree/
https://leetcode-cn.com/problems/symmetric-tree/

Given the root of a binary tree, _check whether it is a mirror of itself
Example 1:

Input: root = [1,2,2,3,4,4,3]
Output: true
Example 2:


Input: root = [1,2,2,null,3,null,3]
Output: false

Solution: recursive check if the root’s left child and right child "mirror"

object LeetCode_101_Symmetric_Tree {  
  def isSymmetric(root: TreeNode): Boolean = {  
  if (root == null) true else {  
  isMirror(root.left, root.right)  
 } }  
  def isMirror(left: TreeNode, right: TreeNode): Boolean = {  
  if (left != null & right != null) {  
  left.value == right.value && isMirror(left.left, right.right) && isMirror(left.right, right.left)  
 } else if (left == null && right == null) true else false  
  }  
}

Test

import org.scalatest.funsuite.AnyFunSuite  
import org.scalatest.matchers.should._  

class LeetCode_101_Symmetric_TreeTest extends AnyFunSuite with Matchers {  
  val tree1 = deserialize("2,1,1")  
  val tree2 = deserialize("2,1,3")  

  test("return true if a tree is symmetric") {  
  isSymmetric(tree1) shouldBe true  
  }  

  test("return false if a tree is not symmetric") {  
  isSymmetric(tree2) shouldBe false  
  }  
}

Binary Tree Order Traversal

Traversal of a Binary-Tree | 101 Computing

The root is the Kth part visited.

Kth
Pre-Order 1st
In-Order 2nd
Post-Order 3rd

: ) Now let us try this tree

Simplest Binary Tree Traversal trick for preorder inorder postorder -  YouTube

In-Order Traversal

Left branch -> Current node -> Right branch

Traversal of a Binary-Tree | 101 Computing

When performed on a binary search tree, it visits the nodes in ascending order (i.e. in order)

https://leetcode.com/problems/binary-tree-inorder-traversal/
https://leetcode-cn.com/problems/binary-tree-inorder-traversal/

Given the root of a binary tree, return the inorder traversal of its nodes’ values.

Example 1:

Input: root = [1,null,2,3]
Output: [1,3,2]

Solution

object LeetCode_94_Binary_Tree_Inorder_Traversal {  
  def inorderTraversal(root: TreeNode): List[Int] = {  
  if (root != null) {  
 {  inorderTraversal(root.left) :+ root.value  
 } ++ inorderTraversal(root.right)  
 } else List()  
 }}

Test

import org.scalatest.funsuite.AnyFunSuite  
import org.scalatest.matchers.should._  

class LeetCode_94_Binary_Tree_Inorder_TraversalTest extends AnyFunSuite with Matchers {  
  test("Inorder traversal result of this tree should be List(1, 3, 2)") {  
  inorderTraversal(deserialize("1,null,2,3")) shouldBe List(1, 3, 2)  
 }}

Pre-Order Traversal

Visits the current node before its child nodes

Current node -> Left branch -> Right branch

Traversal of a Binary-Tree | 101 Computing

The root is always the first node visited.

https://leetcode-cn.com/problems/binary-tree-preorder-traversal/
https://leetcode.com/problems/binary-tree-preorder-traversal/

Given the root of a binary tree, return the preorder traversal of its nodes’ values.

Example 1:

Input: root = [1,null,2,3]
Output: [1,2,3]

Solution

object LeetCode_144_Binary_Tree_Preorder_Traversal {  
  def preorderTraversal(node: TreeNode): List[Int] = {  
  if (node != null) {  
  node.value :: preorderTraversal(node.left) ++ preorderTraversal(node.right)  
 } else List()  
 }}

Test

import org.scalatest.funsuite.AnyFunSuite  
import org.scalatest.matchers.should._  

class LeetCode_144_Binary_Tree_Preorder_TraversalTest extends AnyFunSuite with  
  Matchers {  
  test("Preorder traversal result of this tree should be List(1, 2, 3)") {  
  preorderTraversal(deserialize("1,null,2,3")) shouldBe List(1, 2, 3)  
 }}

Post-Order Traversal

Visits the current node after its child nodes

Left branch -> Right branch -> Current node

Traversal of a Binary-Tree | 101 Computing

The root is always the last node visited.

https://leetcode.com/problems/binary-tree-postorder-traversal/
https://leetcode-cn.com/problems/binary-tree-postorder-traversal/

Given the root of a binary tree, return the postorder traversal of its nodes’ values.

Example 1:

Input: root = [1,null,2,3]
Output: [3,2,1]

Solution

object LeetCode_145_Binary_Tree_Postorder_Traversal {  
  def postOrderTraversal(node: TreeNode): List[Int] = {  
  if (node != null) {  
  postOrderTraversal(node.left) ++ postOrderTraversal(node.right) :+ node.value  
  // The root is always the last node visited.
 } else List()  
 }}

Test

import org.scalatest.funsuite.AnyFunSuite  
import org.scalatest.matchers.should._  

class LeetCode_145_Binary_Tree_Postorder_TraversalTest extends AnyFunSuite with  
  Matchers {  
  test("Preorder traversal result of this tree should be List(3, 2, 1)") {  
  postOrderTraversal(deserialize("1,null,2,3")) shouldBe List(3, 2, 1)  
 }}

Level Order Traversal

Given the root of a binary tree, return the level order traversal of its nodes' values. (i.e., from left to right, level by level).

https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
https://leetcode.com/problems/binary-tree-level-order-traversal/

Example:

Input: root = [3,9,20,null,null,15,7]
Output: [[3],[9,20],[15,7]]

Solution 1: Functional programming (immutable)

Well, I am a Scala person

import scala.annotation.tailrec  

object LeetCode_102_Binary_Tree_Level_Order_Traversal {  

  def levelOrder(root: LeetCodeCN.TreeNode): List[List[Int]] = {  
  val currentTraversalResult = List()  
  if (root != null) {  
  traversal(List(root), currentTraversalResult)  
 } else currentTraversalResult  
  }  

  @tailrec  
  def traversal(currentLevel: List[LeetCodeCN.TreeNode], currentTraversalResult: List[List[Int]]): List[List[Int]] = {  
  if (currentLevel.nonEmpty) {  
  val newTraversalResult = currentTraversalResult :+ currentLevel.map(_.value)  
  val nextLevel: List[LeetCodeCN.TreeNode] = currentLevel.map(node => List(node.left, node.right)).flatten.filter(_ != null)  
  traversal(nextLevel, newTraversalResult)  
 } else currentTraversalResult  
  }
  }

Solution 2: Use Queue and ListBuffer (mutable)


import scala.collection.mutable  
import scala.collection.mutable.ListBuffer  

object LeetCode_102_Binary_Tree_Level_Order_Traversal {
  def levelOrder(root: TreeNode): List[List[Int]] = {  
  if (root == null) return List[List[Int]]()  
  val result = new ListBuffer[List[Int]]()  
  val queue = new mutable.Queue[TreeNode]()  
  queue.enqueue(root)  
  while (queue.nonEmpty) {  
  val levelResult = new ListBuffer[Int]()  
  var levelSize = queue.size  
        while (levelSize > 0) {  
  val item = queue.dequeue()  
  levelResult.append(item.value)  
  if (item.left != null) {  
  queue.enqueue(item.left)  
 }  if (item.right != null) {  
  queue.enqueue(item.right)  
 }  levelSize = levelSize - 1  
  }  
  result.append(levelResult.toList)  
 }  result.toList  
    }  

}

Test

import org.scalatest.funsuite.AnyFunSuite  
import org.scalatest.matchers.should._  

class LeetCode_102_Binary_Tree_Level_Order_TraversalTest extends AnyFunSuite with  
  Matchers {  
  test("Level order traversal result of this tree should be  [[3],[9,20],[15,7]]") {  
  levelOrder(deserialize("3,9,20,null,null,15,7")) shouldBe List(List(3), List(9,  
      20), List(15, 7))  
 }}

Serialize and Deserialize Trees

Serialization is converting a data structure or object into a sequence of bits to be stored in a file or memory buffer or transmitted across a network connection link to be reconstructed later in the same or another computer environment.

Construct String from Binary Tree

https://leetcode-cn.com/problems/construct-string-from-binary-tree/

Example 1:

Input: root = [1,2,3,4]
Output: "1(2(4))(3)"
Explanation: Originally, it needs to be "1(2(4)())(3()())", but you need to omit all the unnecessary empty parenthesis pairs. And it will be "1(2(4))(3)"


val four = TreeNode(4, null, null)  
val three = TreeNode(3, null, null)  
val two = TreeNode(2, four, null)  
val one = TreeNode(1, two, three)  

val emptyStr = ""  
val emptyNode = "()"  

def tree2str(root: TreeNode): String = {  
  if (root == null) emptyStr else {  
    val left = root.left  
  val right = root.right  
  val (leftStr, rightStr) = (left, right) match{  
      case (null, null) => (emptyStr,emptyStr)  
      case (left, null) => (s"(${tree2str(root.left)})", emptyStr)  
      case (null, right) => (emptyNode, s"(${tree2str(root.right)})")  
      case (left, right) => (s"(${tree2str(root.left)})", s"(${tree2str(root.right)})")  
    }  
    s"${root.value}" + leftStr + rightStr  
  }  
}  

println(tree2str(one))

Find Duplicate Subtrees

https://leetcode.com/problems/find-duplicate-subtrees/
https://leetcode-cn.com/problems/find-duplicate-subtrees/

Given the root of a binary tree, return all duplicate subtrees.

For each kind of duplicate subtrees, you only need to return the root node of any one of them.

Two trees are duplicate if they have the same structure with the same node values.

Example 1:

Input: root = [1,2,3,4,null,2,4,null,null,4]
Output: [[2,4],[4]]

Example 2:

Input: root = [2,1,1]
Output: [[1]]

Example 3:

Input: root = [2,2,2,3,null,3,null]
Output: [[2,3],[3]]

  val rightOne = TreeNode(1, null, null)  
  val leftOne = TreeNode(1, null, null)  
  val two = TreeNode(2, leftOne, rightOne)  

  val emptyNode = "()"  

  var dic: Map[String, (TreeNode, Int)] = Map()  

//    [2,1,11,11,null,1]  
  def tree2str(root: TreeNode): String = {  
    if (root == null) emptyNode else {  
      s"${root.value}(${tree2str(root.left)}(${tree2str(root.right)}"  
  }  
  }  

  def updateNodeDictionary(root: TreeNode): Unit = {  
    if (root != null) {  
      val nodeId = tree2str(root)  
      val nodeCount = dic.get(nodeId)  
      val newNodeCount = if (nodeCount.nonEmpty) {  
        nodeCount.get._2 + 1  
  } else {  
        1  
  }  
      dic += nodeId -> (root, newNodeCount)  
      updateNodeDictionary(root.left)  
      updateNodeDictionary(root.right)  
    }  
  }  

  def findDuplicateSubtrees(root: TreeNode): List[TreeNode] = {  
    updateNodeDictionary(root)  
    val duplicatedNodes: List[TreeNode] = dic.toSeq.collect { case (nodeId, (node, count)) if (count > 1) => node }.toList  
    dic = Map()  
    duplicatedNodes  
  }  

  println(findDuplicateSubtrees(two).map(_.value))

Subtree of Another Tree

https://leetcode.com/problems/subtree-of-another-tree/

Given the roots of two binary trees, root and sub root, return true if there is a subtree of root with the same structure and node values of sub root and false otherwise.

A subtree of a binary tree is a tree consisting of a node in the tree and all of this node’s descendants. The tree could also be considered as a subtree of itself.

Example 1:

Input: root = [3,4,5,1,2], subRoot = [4,1,2]
Output: true
Example 2:

Input: root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
Output: false

Example 2:

Input: root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
Output: false

Solution: Serialize all subtrees of the root, and store them in a set. Then, check if the subRoot is in the set.

val two = TreeNode(2, null, null)  
val one = TreeNode(1, null, null)  
val four = TreeNode(4, one, two)  
val five = TreeNode(5, null, null)  
val three = TreeNode(3, four, five)  
val six = TreeNode(6, null, null)  

val emptyNode = "()"  
var subNodeSet: Set[String] = Set()  

def tree2str(root: TreeNode): String = {  
  if (root == null) emptyNode else {  
    s"${root.value}(${tree2str(root.left)}(${tree2str(root.right)}"  
  }  
}  

def updateSubTreeSet(root: TreeNode): Unit = {  
  if (root != null) {  
    val nodeId = tree2str(root)  

    subNodeSet += nodeId  

  updateSubTreeSet(root.left)  
    updateSubTreeSet(root.right)  
  }  
}  

def isSubtree(root: TreeNode, subRoot: TreeNode): Boolean = {  
  val serializedSubRoot = tree2str(subRoot)  
  updateSubTreeSet(root)  
  val isSubtree = subNodeSet.contains(serializedSubRoot)  
  subNodeSet = Set()  
  isSubtree  
}  

println(isSubtree(three, four)) // true  
println(isSubtree(three, six)) // false

Find Depth / Diameter of Trees

Example Tree:

 class TreeNode(_value: Int = 0, _left: TreeNode = null, _right: TreeNode = null) {  
  var value: Int = _value  
  var left: TreeNode = _left  
  var right: TreeNode = _right  
}  

  val seventeen = TreeNode(17, null, null)  
  val fifteen = TreeNode(15, null, null)  
  val twenty = TreeNode(20, fifteen, seventeen)  
  val nine = TreeNode(9, null, null)  
  val root = TreeNode(3, nine, twenty)  

Find Max Depth of Binary Tree

https://leetcode.com/problems/maximum-depth-of-binary-tree/

Given the root of a binary tree, return its maximum depth.
A binary tree’s maximum depth is the number of nodes along the longest path from the root node to the farthest leaf node.

Input: root = [3,9,20,null,null,15,7]
Output: 3

def maxDepth(root: TreeNode): Int = {  
  if (root == null) 0 else {  
    Math.max(maxDepth(root.left), maxDepth(root.right)) + 1  
  }  
}  
println(maxDepth(root)) // 3

Find Min Depth of Binary Tree

https://leetcode.com/problems/minimum-depth-of-binary-tree/

Find Min Depth of a Binary Tree

Use if-else

def minDepth(root: TreeNode): Int = {  
  if (root == null) 0 else {  
    if (root.left != null && root.right != null) {  
      Math.min(minDepth(root.left), minDepth(root.right)) + 1  
  } else if (root.left != null) {  
      minDepth(root.left) + 1  
  } else if (root.right != null) {  
      minDepth(root.right) + 1  
  } else 1  
  }  
}  

println(minDepth(root)) // 2

Use matching

def minDepth(root: TreeNode): Int = {  
  if (root == null) 0 else {  
    val left = root.left  
  val right = root.right  
  (left, right) match {  
      case (null, null) => 1  
  case (null, right) => minDepth(right) + 1  
  case (left, null) => minDepth(left) + 1  
  case (left, right) => Math.min(minDepth(left), minDepth(right)) + 1  
  }  
  }  
}

println(minDepth(root)) // 2

Find diameter of Binary Tree

Given the root of a binary tree, return the length of the diameter of the tree.

The diameter of a binary tree is the length of the longest path between any two nodes in a tree. This path may or may not pass through the root.

The number of edges between them represents the length of a path between two nodes.

https://leetcode.com/problems/diameter-of-binary-tree/

Example 1:

Input: root = [1,2,3,4,5]

Output: 3

Explanation: 3 is the length of the path [4,2,1,3] or [5,2,1,3].

Solution

import scala.math.max  
var diameters: Set[Int] = Set()  

def maxDiameter(root: TreeNode): Int = {  
  if (root == null) 0 else {  
  val left = root.left  
  val right = root.right  
 (left, right) match {  
  case (null, null) => 1  
  case (left, null) => 1 + maxDiameter(left)  
  case (null, right) => 1 + maxDiameter(right)  
  case (left, right) => max(maxDiameter(right), maxDiameter(left)) + 1  
  }  
 }}  

def addDiameterOfNode(root: TreeNode): Unit = {  
  val diameter = if (root == null) 0 else {  
  val left = root.left  
  val right = root.right  
 (left, right) match {  
  case (null, null) => 0  
  case (left, null) => maxDiameter(left)  
  case (null, right) => maxDiameter(right)  
  case (left, right) => maxDiameter(right) + maxDiameter(left)  
 } } diameters += diameter  
}  

def traversal(root: TreeNode): Unit = {  
  if (root != null) {  
  addDiameterOfNode(root)  
  addDiameterOfNode(root.left)  
  addDiameterOfNode(root.right)  
 }}  

def diameterOfBinaryTree(root: TreeNode): Int = {  
  traversal(root)  
  val diameter = diameters.max  
  diameters = Set()  
  diameter  
}

Test

class LeetCode_543_Diameter_of_Binary_TreeTest extends AnyFunSuite {  
  val tree1 = "4,-7,-3,null,null,-9,-3,9,-7,-4,null,6,null,-6,-6,null,null,0,6,5,null,9,null,null,-1,-4,null,null,null,-2"  
  val tree2 = "1,2,3,null,null,4,5"  

  test("Return 3 for this tree's diameter") {  
  val tree = deserialize(tree2)  
  assert(diameterOfBinaryTree(tree) == 3)  
 }  
  test("Return 7 for this tree's diameter") {  
  val tree = deserialize(tree1)  
  assert(diameterOfBinaryTree(tree) == 7)  
 }}

Relationships between Trees

Identical Trees

Two binary trees are considered the same if they are structurally identical, and the nodes have the same value.

https://leetcode.com/problems/same-tree/

Example 1:

Input: p = [1,2,3], q = [1,2,3]
Output: true

Example 2:

Input: p = [1,2], q = [1,null,2]
Output: false

Example 3:

Input: p = [1,2,1], q = [1,1,2]
Output: false

Solution

The most straightforward strategy here is to use recursion. Check if p and q nodes are not "None", and their values are equal. If all checks are OK, do the same for the child nodes recursively.


def isSameTree(p: TreeNode, q: TreeNode): Boolean = {  
  (p, q) match {  
    case (null, null) => true  
 case (p, null) => false  
 case (null, q) => false  
 case (p, q) => {  
      p.value == q.value && isSameTree(p.left, q.left) && isSameTree(p.right, q.right)  
    }  
  }  
}  

Test

import main.scala.LeetCodeCN.LeetCode_100_Same_Tree.isSameTree  
import main.scala.LeetCodeCN.TreeNode  
import org.scalatest.funsuite.AnyFunSuite  

class LeetCode_100_Same_TreeTest extends AnyFunSuite {  
  val three = TreeNode(3, null, null)  
  val one = TreeNode(1, null, null)  
  val two = TreeNode(2, one, three)  

  test("return true if two trees are the same") {  
  assert(isSameTree(two, two) == true)  
 }  
  test("return false if two trees are not the same") {  
  assert(isSameTree(one, two) == false)  
 }}

Process Trees

Merge Two Binary Trees

https://leetcode-cn.com/problems/merge-two-binary-trees/
https://leetcode.com/problems/merge-two-binary-trees/

Other Problem Solving

Path Sum

https://leetcode.com/problems/path-sum/

Given the root of a binary tree and an integer targetSum, return true if the tree has a root-to-leaf path such that adding up all the values along the path equals targetSum. (A leaf is a node with no children.)

Example :

Input: root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
Output: true

 class TreeNode(_value: Int = 0, _left: TreeNode = null, _right: TreeNode = null) {  
  var value: Int = _value  
  var left: TreeNode = _left  
  var right: TreeNode = _right  
} 

// Build tree
val two = TreeNode(2, null, null)  
val seven = TreeNode(7, null, null)  
val eleven = TreeNode(11, seven, two)  
val fourLeft = TreeNode(4, eleven, null)  
val one = TreeNode(1, null, null)  
val fourRight = TreeNode(4, null, one)  
val thirteen = TreeNode(13, null, null)  
val eight = TreeNode(8, thirteen, fourRight)  
val root = TreeNode(5, fourLeft, eight)  

def hasPathSum(root: TreeNode, targetSum: Int): Boolean = {  
  if (root == null) {  
    if (targetSum == 0) true else false  
  } else {  
    val left = root.left  
  val right = root.right  
  val rootValue = root.value  
  hasPathSum(left, targetSum - rootValue) || hasPathSum(right, targetSum - rootValue)  
  }  
}  

println(hasPathSum(root, 22))  // true
println(hasPathSum(root, 26))  // true
println(hasPathSum(root, 25))  // false 

Reference

  1. McDowell, G., n.d. Cracking the coding interview.
  2. En.wikipedia.org. 2021. Tree (data structure) – Wikipedia. [online] Available at: https://en.wikipedia.org/wiki/Tree_(data_structure) [Accessed 12 November 2021].
  3. En.wikipedia.org. 2021. Binary search tree – Wikipedia. [online] Available at: https://en.wikipedia.org/wiki/Binary_search_tree [Accessed 12 November 2021].
  4. Lcm.csa.iisc.ernet.in. 2021. 6.1.1 Implementation of Insert and Deletemin. [online] Available at: http://lcm.csa.iisc.ernet.in/dsa/node138.html [Accessed 14 November 2021].
  5. Medium. 2021. Binary Heap Basics. [online] Available at: https://medium.com/@verdi?p=40a0f3f41c8f [Accessed 14 November 2021].
  6. En.wikipedia.org. 2021. Trie – Wikipedia. [online] Available at: https://en.wikipedia.org/wiki/Trie [Accessed 14 November 2021].
  7. DroidTechKnow. 2021. An Introduction To Graph Data Structure. [online] Available at: https://droidtechknow.com/programming/data-structure/an-introduction-to-graph-data-structure/ [Accessed 14 November 2021].
  8. Leetcode-cn.com. 2021.. [online] Available at: https://leetcode-cn.com/ [Accessed 19 November 2021].
  9. Techie Me. 2021. Balanced Search Trees – AVL Tree – Techie Me. [online] Available at: http://techieme.in/balanced-search-trees-avl-tree/ [Accessed 24 November 2021].
  10. Programmercarl.com. 2021. _. [online] Available at: https://programmercarl.com/ [Accessed 28 November 2021].
  11. TechTalk7. 2021. Real world examples of tree structures – TechTalk7. [online] Available at: https://www.techtalk7.com/real-world-examples-of-tree-structures/ [Accessed 10 December 2021].
  12. Btechsmartclass.com. 2021. Data Structures Tutorials – Tree Terminology with examples. [online] Available at: http://www.btechsmartclass.com/data_structures/tree-terminology.html [Accessed 10 December 2021].

Leave a Reply

Your email address will not be published. Required fields are marked *