Table of contents
1.
Introduction 
2.
Problem Statement
2.1.
Understanding the Problem Statement
3.
Algorithm 
3.1.
Code in C++
3.2.
Code in Java
3.3.
Complexity Analysis 
4.
Frequently Asked Questions
4.1.
What is recursion?
4.2.
Why is Tree called a non-linear data structure?
4.3.
What is the difference between a binary tree and a binary search tree?
5.
Conclusion
Last Updated: Mar 27, 2024
Medium

Compress a Binary Tree Based on an Overlapping Condition

Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction 

Almost every company is obsessed with trees, and coding interviews frequently involve questions about them. 

The problem we'll be covering today is also a tree problem and a fascinating one too.

Compress a Binary Tree Based on an Overlapping Condition

In this article, we will be discussing a problem based on binary trees. A binary tree is a data structure with each node having at most two children, known as left and right child. 

So, let us jump right into the problem statement: 

Problem Statement

Given a binary tree, the task is to compress or overlap all the nodes on the same vertical line into a single node so that if the total number of set bits of all the nodes on a vertical line at any position is greater than the total number of clear bits at that position, the bit of the single node at that position is set.

Understanding the Problem Statement

In this problem, we are given a binary tree, and our task is to compress all the nodes on the same vertical line into a single node.

Let us take an example to understand the problem: 

Binary tree

We need to calculate the horizontal distance of each node from the root node. 

The horizontal distance of a node depends on the path taken from the root to reach it such that for every right turn, one is added, and for every left turn, one is subtracted. 

Binary tree with Horizontal distances

Now, those who have the same horizontal distance will be gathered as a group, and then the overlapping condition will be applied. Based on the no. of set and clear bits present, the decision will be taken. 

Binary tree with horizontal distances

In the image above, Root node - 1 and node - 5 have the same horizontal distance.

Now, we need to check the binary representation of the nodes. 

Binary representation of numbers

The next step is to calculate the set(1) and clear(0) bit at each position. 

Binary representation of numbers with positions

Now, position-wise, count the number of bits. As per the problem statement, if the no. of set bits is greater than the clear bits, then we will set the bit at that position in the result. 

Binary representation of numbers with positions

Having said that, there will be three cases: 

  1. SET BITS > CLEAR BITS : Result bit will be set to 1. 
  2. SET BITS < CLEAR BITS : Result bit will be set to 0. 
  3. SET BITS = CLEAR BITS Result bit will be set to 0. 
Binary representation of numbers with positions

We will do the same procedure for the rest of the nodes. If any node has no partner with the same horizontal distance, then no overlapping will be performed. 

Note: We are scanning the tree from the leftmost side. 

The output of the above binary tree will be:

Binary tree with horizontal distances and result

Let us now look at the algorithm of the same.  

Algorithm 

The algorithm works by traversing the binary tree and mapping all the nodes on the same vertical line to the horizontal distance. It then compresses the nodes on each vertical line by counting the number of set bits and clear bits at each bit position and setting the corresponding bit of the compressed node

Below are the steps that we can follow to solve the problem: 

  1. We start by defining a binary tree using the Node class, which has a value (val) and left and right pointers (left, right) that point to the left and right subtrees, respectively.
     
  2. We then define a function BTraversal that recursively traverses the binary tree and maps all the nodes of the same vertical line to the horizontal distance using a map. The map is used to store the nodes for each horizontal distance, where the horizontal distance is defined as the distance of the node from the root node in terms of the number of left or right turns.
     
  3. We define a utility function evaluateComp, that compresses all the nodes on the same vertical line with a single node that satisfies a condition. This function takes a vector arr as input, which contains all the nodes on the same vertical line.
     
  4. To compress the nodes, we iterate over the range of bit positions from 0 to 31

    For each bit position i, we count the number of set bits and clear bits at that position for all the nodes in the vector arr

    If the count of set bits is greater than the count of clear bits, we set the ith bit of the compressed node to 1

    Otherwise, we set the ith bit of the compressed node to 0.
     
  5. We then define the compressBTree function that uses the BTraversal function to map all the nodes on the same vertical line and the evaluateComp function to compress the nodes on each vertical line.
     
  6. Finally, we create a binary tree and call the compressBTree function to compress the nodes on each vertical line.
     

Let us look at the implementation of the above algorithm and let us find out the complexity analysis. 

Code in C++

/* C++ program for
Compress a Binary Tree 
Based on an Overlapping Condition */

#include <bits/stdc++.h>
using namespace std;

/* defining the structure of a node of the tree */

class Node {
  public: int val;
  
  /* define pointers for 
  left and right subtrees */
  
  Node * left,
  * right;

  Node(int val) {
    this -> val = val;
    left = right = NULL;
  }
};

/* Utility Function to compress all the nodes on the same vertical  line */

void evaluateComp(vector < int > & arr) {

  /* store node by compressing all nodes on the current
  vertical line */
  
  int result = 0;

  // to check if ith bit of current number set or not
  int getBit = 1;

  // Iterate over the range [0, 31]
  for (int i = 0; i < 32; i++) {
  
    // store count of set bits at ith positions
    
    int SB = 0;
    
    // store count of clear bits at ith positions
    
    int CB = 0;

    // loop to count the number of set and clear bits
    
    for (auto j: arr) {
    
      // If ith bit of current element is set
      
      if (getBit & j)
      
        SB++; // Update SB

      else
        CB++; // Update CB
    }
    /* If the count of set bits at ith position is greater
    than count of clear bits, then
    set the resultant bit as set */

    if (SB > CB)
      // Update result by setting the ith bit
      result += pow(2, i);

    // Update getBit
    getBit <<= 1;
  }
  cout << result << " ";
}

/* Function to traverse the tree and map all the nodes of
same vertical line to horizontal distance */

void BTraversal(Node * root, int hd, map < int, vector < int > > & mp) {
  if (!root)
    return;
    
  // Storing the values in the map
  
  mp[hd].push_back(root -> val);

  // Recursive calls on left and right subtree
  
  BTraversal(root -> left, hd - 1, mp);
  BTraversal(root -> right, hd + 1, mp);
}

/* Function to compress all the nodes on the same vertical
line with a single node that satisfies the condition */

void compressBTree(Node * root) {

  // Map all the nodes on the same vertical line
  map < int, vector < int > > mp;
  
  BTraversal(root, 0, mp);
  
  // Getting the range of horizontal distances
  int lower, upper;
  
  for (auto i: mp) {
  
    lower = min(lower, i.first);
    upper = max(upper, i.first);
  }

  for (int i = lower; i <= upper; i++)
    evaluateComp(mp[i]);
}

// Driver Code

int main() {
  Node * root = new Node(1);
  root -> left = new Node(2);
  root -> right = new Node(3);
  root -> left -> left = new Node(4);
  root -> left -> right = new Node(5);
  root -> right -> right = new Node(6);
  root -> right -> right -> right = new Node(8);

  // Function Call
  compressBTree(root);
  
  return 0;
}
You can also try this code with Online C++ Compiler
Run Code

Code in Java

import java.util.*;
// Structure of a node of the tree

class Node {
    int val;
    Node left, right;
    
    Node(int val) {
        this.val = val;
        left = right = null;
    }
}

// Class to store the compressed node and its count

class CompressedNode {
    int node;
    int count;
    
    CompressedNode(int node, int count) {
        this.node = node;
        this.count = count;
    }
}


// Main class
class BinaryTreeCompression {
    
    /* Function to compress the nodes with a single node 
  that meets the condition */
  
    static void evaluateComp(List<Integer> arr) {
    
        /* store node by compressing all nodes on the current
        vertical line */
        
        int result = 0;


        // Check if ith bit of current bit set or not
        int getBit = 1;


        // Iterate over the range [0, 31]
        
        for (int iter = 0; iter < 32; iter++) {
        
            // store count of set bits at ith positions
            int SB = 0;
            // store count of clear bits at ith positions
            int CB = 0;


            // Traverse the array
            
            for (int j : arr) {
            
                // If ith bit of current element is set
                
                if ((getBit & j) != 0)
                    SB++; // Update SB
                else
                    CB++; // Update CB
            }
            
            /* If count of set bits at ith position is greater
            than count of clear bits */
            
            if (SB > CB)
                // Update result
                result += Math.pow(2, iter);


            // Update getBit
            getBit <<= 1;
        }


        System.out.print(result + " ");
    }
    
    /* Function to traverse the tree and map all the nodes of
    same vertical line to horizontal distance */
    
    static void BTraversal(Node root, int hd, Map<Integer, List<Integer>> mp) {
        if (root == null)
            return;


        // Storing the values in the map
        
        List<Integer> values = mp.getOrDefault(hd, new ArrayList<>());
        values.add(root.val);
        mp.put(hd, values);


        // Recursive calls on left and right subtree
        
        BTraversal(root.left, hd - 1, mp);
        BTraversal(root.right, hd + 1, mp);
    }
    
    // Function to compress the nodes with a single node
    
    static void compressBTree(Node root) {
    
        // Map all the nodes on the same vertical line
        
        Map<Integer, List<Integer>> mp = new HashMap<>();
        BTraversal(root, 0, mp);


        // Getting the range of horizontal distances
        
        int lower = Integer.MAX_VALUE, upper = Integer.MIN_VALUE;
        
        for (int i : mp.keySet()) {
        
            lower = Math.min(lower, i);
            upper = Math.max(upper, i);
        }


        for (int i = lower; i <= upper; i++) {
            evaluateComp(mp.get(i));
        }
    }

    // Driver Code
    
    public static void main(String[] args) {
    
        Node root = new Node(1);
        root.left = new Node(2);
        root.right = new Node(3);
        root.left.left = new Node(4);
        root.left.right = new Node(5);
        root.right.right = new Node(6);
        root.right.right.right = new Node(8);


        // Function Call
        compressBTree(root);
    }
}
You can also try this code with Online Java Compiler
Run Code


Output

4 2 1 3 6 8 

Complexity Analysis 


Time Complexity: 

O(N log N), where N is the number of nodes in the binary tree. 

This is because the function involves a recursive traversal of the entire tree, and for each node, it performs a constant amount of work that involves updating a map and performing bitwise operations.


Space Complexity: 

O(N), where N is the number of nodes in the binary tree. 

This is because the function uses a HashMap to store the nodes mapped to their respective horizontal distances, and the size of the map can be, at most, N. 

Additionally, the function uses a List or vector to store the values in the map, which can also have a maximum size of N. 

Therefore, the total space complexity is O(N * N) = O(N2).

Frequently Asked Questions

What is recursion?

Recursion is a programming technique where a function calls itself. This is often used to solve problems that can be broken down into smaller, simpler sub-problems.

Why is Tree called a non-linear data structure?

A tree is called a non-linear data structure because it does not store data in a linear sequence like an array or a linked list. Instead, it organizes data hierarchically, with a parent-child relationship between nodes.

What is the difference between a binary tree and a binary search tree?

A binary tree is a tree where each node has at most two children. On the other hand, a binary search tree is a binary tree where the value of each node is greater than or equal to the values of all the nodes in its left subtree and less than or equal to the values of all the nodes in its right subtree. 

Conclusion

To conclude the discussion, we have extensively looked at the problem based on binary trees, i.,e, Compress a Binary Tree Based on an Overlapping Condition. We used mapping to solve the problem. At last, we also looked at the complexity analysis of the approach. 

Check out this problem - Connect Nodes At Same Level

On the Coding Ninjas Platform, we have various such problems. Additionally, Coding Ninjas recently introduced the Coding Ninjas Studio Test Series, a mainly developed test series for acing interviews.

Thank you for taking the time to read this. I hope this blog has been helpful to you.

Live masterclass