Constructing an array of smaller elements than the corresponding elements based on input array in JavaScript


In the realm of JavaScript programming, the ability to construct an array of smaller elements than their corresponding counterparts from an input array holds immense significance. This intriguing technique allows developers to manipulate and transform data, opening doors to innovative solutions and efficient algorithms. By harnessing the power of JavaScript's array manipulation capabilities, developers can generate new arrays with elements that are scaled down or reduced based on the values of the original array. In this article, we delve into the art of constructing an array of smaller elements than the corresponding elements from an input array, exploring the underlying concepts and utilizing rarely used methods to achieve elegant and resourceful solutions.

Problem Statement

Our task is to create a JavaScript function that takes an input array of numbers and constructs an output array based on it. The function iterates through each element of the input array and calculates the count of numbers smaller than the current element but located to its right. The output array is constructed in a way that each element at the corresponding index represents the count of smaller numbers to the right of the corresponding element in the input array.

Sample Input −

Input Array: [5, 2, 6, 1]

Sample Output −

Output Array: [2, 1, 1, 0]

In the given example, the input array is [5, 2, 6, 1]. For the first element 5, there are two numbers (2 and 1) that are smaller than 5 and appear to its right in the input array. Similarly, for the second element 2, there is one number (1) that is smaller and appears to its right. For the third element 6, there is one number (1) smaller than 6 to its right. Finally, for the fourth element 1, there are no numbers smaller than 1 to its right. Hence, the output array is [2, 1, 1, 0].

Approach

In this article, we are going to see a number of different ways to solve the above problem statement in JavaScript −

  • Brute Force

  • Binary Indexed Tree

  • Binary Search Tree

Method 1: Brute Force

The brute force approach counts the numbers smaller than each element in the input array by iterating over the array and using nested loops to compare each pair of elements. It increments a count variable whenever a smaller number is found and stores the counts in the output array. This approach has a time complexity of O(n^2), where n is the size of the input array, due to its nested iterations.

Example

The countSmallerNumbersToRight function accepts an input array and initializes an empty output array. It uses two nested loops to iterate over each element in the input array and count the numbers smaller than the current element to its right. The outer loop goes through each element, while the inner loop starts from the next element and increments the count if it is smaller. After the inner loop finishes for a particular element, it adds the count to the output array. Ultimately, the function returns the output array.

function countSmallerNumbersToRight(inputArray) {
   const outputArray = [];
   for (let i = 0; i < inputArray.length; i++) {
      let count = 0;
      for (let j = i + 1; j < inputArray.length; j++) {
         if (inputArray[j] < inputArray[i]) {
            count++;
         }
      }
      outputArray.push(count);
   }
   return outputArray;
}
 
const arr = [6, 2, 8, 5, 1, 3];
console.log(countSmallerNumbersToRight(arr));

Output

The following is the console output −

[ 4, 1, 3, 2, 0, 0 ]

Method 2: Binary Indexed Tree

Using a binary indexed tree (Fenwick tree), this approach efficiently calculates the count of numbers smaller than each element. It creates a Fenwick tree with a size equal to the maximum element in the input array and traverses the array from right to left. For each element, it updates the corresponding index in the Fenwick tree and uses prefix sum queries to calculate the counts. The results are stored in the output array, representing the count of smaller numbers to the right for each element. This method has a time complexity of O(n log k), where n is the input array's size and k is the maximum element in the array.

Example

The function countSmallerNumbersToRight takes an input array and defines two helper functions: updateBIT and queryBIT. updateBIT updates the binary indexed tree (BIT) with an index and value, while queryBIT calculates the prefix sum up to a given index in the BIT. The function initializes the maximum element in the input array and creates a BIT array with a size of the maximum element plus one. It then traverses the input array from right to left, querying the BIT for counts of smaller numbers and updating the BIT with the current element. The counts are added to the outputArray, which is returned at the end.

function countSmallerNumbersToRight(inputArray) {
   function updateBIT(BIT, index, value) {
      while (index < BIT.length) {
         BIT[index] += value;
         index += index & -index;
      }
   }
   function queryBIT(BIT, index) {
      let count = 0;
      while (index > 0) {
         count += BIT[index];
         index -= index & -index;
      }
      return count;
   }

   const maxElement = Math.max(...inputArray);
   const BIT = new Array(maxElement + 1).fill(0);
   const outputArray = [];

   for (let i = inputArray.length - 1; i >= 0; i--) {
      outputArray.unshift(queryBIT(BIT, inputArray[i] - 1));
      updateBIT(BIT, inputArray[i], 1);
   }
   return outputArray;
}

const arr = [6, 2, 8, 5, 1, 3];
console.log(countSmallerNumbersToRight(arr));

Output

The following is the console output −

[ 4, 1, 3, 2, 0, 0 ]

Method 3: Binary Search Tree

To count the numbers smaller than each element using a binary search tree (BST), an empty BST is created and the output array is initialized. Starting from the second last element of the input array, each element is inserted into the BST. During insertion, the count of smaller elements encountered is tracked. If the element is smaller than the current node, the count is incremented and the element is recursively inserted in the left subtree. If the element is greater, the count is added to the current count of smaller elements, as well as the count on the left side of the current node, and the element is recursively inserted in the right subtree. The count of smaller elements is stored in the output array in reverse order. The time complexity of this approach is O(n log n), where n is the input array size, due to the average O(log n) insertion time in a balanced BST for each element.

Example

The Node class represents a node in a binary search tree (BST) and tracks the node value, count of smaller elements on its left side, and left and right child nodes. The insert function recursively inserts a value into the BST, updating the count of smaller elements encountered. If the value is smaller, it increments the leftCount and recursively inserts it in the left subtree, creating a new node if necessary. If the value is greater, it increments the count by the leftCount and inserts it in the right subtree, creating a new node if needed. The countSmallerNumbersToRight function initializes an outputArray, creates the BST root node, and adds 0 to the outputArray. It then iterates through the input array, calling insert to modify the BST and update the outputArray. Finally, the outputArray is returned.

class Node {
   constructor(value) {
      this.value = value;
      this.leftCount = 0;
      this.left = null;
      this.right = null;
   }
}
function insert(node, value, count, outputArray) {
   if (value < node.value) {
      node.leftCount++;
      if (node.left === null) {
         node.left = new Node(value);
         outputArray.unshift(count);
      } else {
         insert(node.left, value, count, outputArray);
      }
   } else {
      count += node.leftCount;
      if (value > node.value) {
         count++;
      }
      if (node.right === null) {
         node.right = new Node(value);
         outputArray.unshift(count);
      } else {
         insert(node.right, value, count, outputArray);
      }
   }
}
function countSmallerNumbersToRight(inputArray) {
   const outputArray = [];
   const root = new Node(inputArray[inputArray.length - 1]);
   outputArray.push(0);
   for (let i = inputArray.length - 2; i >= 0; i--) {
      insert(root, inputArray[i], 0, outputArray);
   }
   return outputArray;
}

const arr = [6, 2, 8, 5, 1, 3];
console.log(countSmallerNumbersToRight(arr));

Output

The following is the console output −

[ 4, 1, 3, 2, 0, 0 ]

Conclusion

In conclusion, the process of constructing an array of diminutive elements based on the input array in JavaScript entails a nuanced approach that promises to augment the versatility of array manipulation. By leveraging lesser-known techniques, such as recursive algorithms or esoteric functions, developers can engender a new array with elements of diminished magnitude relative to their counterparts in the original array. This intricate methodology not only fosters creative problem-solving but also demonstrates the vast potential of JavaScript as a programming language. Thus, by embracing these lesser-explored avenues, developers can unlock a realm of possibilities for constructing arrays with smaller constituents, adding an element of novelty and ingenuity to their code.

Updated on: 04-Aug-2023

66 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements