Surpasser Count Of Each Element In Array In C++ - C++ Programming Tutorial
C++ Course / Arrays / Surpasser Count Of Each Element In Array In C++

Surpasser Count Of Each Element In Array In C++

BLUF: Mastering Surpasser Count Of Each Element In Array In C++ is a critical step in becoming a proficient C++ developer. This lesson provides a deep dive into the syntax, performance considerations, and real-world applications of this concept.
Key Performance Insight: Surpasser Count Of Each Element In Array In C++

C++ is renowned for its efficiency. Learn how Surpasser Count Of Each Element In Array In C++ enables low-level control and high-performance computing in the tutorial below.

A core challenge in algorithmic problem-solving involves the surpasser count, which assesses the relative arrangement of elements within an array. This calculation involves determining, for each element in the array, the count of elements to its right that are greater in value. The surpasser count plays a crucial role in scenarios where assessing the significance of elements within a dataset is necessary, such as in data analysis, ranking methodologies, competitive programming, and various other applications.

Secondly, the task of determining surpasser counts presents both instructional and pragmatic value. From an educational perspective, it serves as a means to familiarize students and developers with array navigation, comparisons, and data organization to enhance computational effectiveness. In practical scenarios, this concept finds application across diverse fields such as analyzing stock prices and forecasting upcoming trends based on the outperformance of certain stocks following a specific stock.

For more extensive datasets, a straightforward O(n^2) approach exists where each item is matched against all following items in the collection, but this can be costly. Therefore, to overcome this inefficiency, more sophisticated methods like divide and conquer and Fenwick Trees (Binary Indexed Tree) can be employed. By enhancing the computation of these methods, we can achieve a time complexity of O(nlogn) to handle larger arrays effectively.

Exploring the surpasser count challenge offers a chance to enhance our knowledge of algorithms and data structures. It presents an opportunity to carefully select the appropriate tools for streamlined computation. This particular problem serves as a great illustration of how strategic methods and careful planning can lead to improved efficiency in handling complexity.

Approach-1: Brute Force Approach

The most basic and direct technique for implementing a brute force strategy to determine surpassing counts is quite simple. This method determines the quantity of elements that exceed all elements on their right side. It involves employing two nested loops where the outer loop remains fixed on a specific element, while the inner loop traverses through the remaining elements to tally the greater elements.

The time complexity of this method will be O(n^2) as it involves n iterations of the outer loop and up to n-1 iterations of the inner loop for each element. It is straightforward to comprehend and easy to execute, but it is limited to small datasets due to its O(n^2) time complexity. When beginning with a fundamental problem, brute force is a suitable approach. However, for performance-critical scenarios, more optimized methods such as divide and conquer or Fenwick Tree should be considered as brute force may not be suitable.

Example:

Let's consider an illustration to demonstrate the number of elements in the array that exceed a particular element's value, using the brute force method in C++.

Example

#include <iostream>
#include <vector>
#include <iomanip> // For formatted output
#include <string>
using namespace std;
//Function to display the array
void displayArray(const vector<int>& arr, const string& label) {
    cout << label << ": ";
    for (int val : arr) {
        cout << setw(3) << val << " ";
    }
    cout << endl;
}
//Function to calculate surpasser counts using brute force
vector<int> calculateSurpasserCounts(const vector<int>& arr) {
    int n = arr.size(); // Size of the input array
    vector<int> surpasserCount(n, 0); // Initialize surpasser count array with zeros
    cout << "\nCalculating surpasser counts step-by-step:\n";
    // Outer loop to fix each element
    for (int i = 0; i < n; ++i) {
        cout << "\nElement at index " << i << " (" << arr[i] << "):\n";
        // Inner loop to compare the current element with all elements to its right
        for (int j = i + 1; j < n; ++j) {
            cout << "  Comparing " << arr[i] << " with " << arr[j] << "... ";
            if (arr[j] > arr[i]) {

                cout << "Surpasser found!";
                surpasserCount[i]++;
            } else {

                cout << "Not a surpasser.";
            }
            cout << endl;

        }
        cout << "  Total surpassers for " << arr[i] << ": " << surpasserCount[i] << "\n";
    }
    return surpasserCount; // Return the final counts
}
//Function to take input for the array
vector<int> inputArray() {
    int n;
    cout << "Enter the number of elements in the array: ";
    cin >> n;
    vector<int> arr(n);
    cout << "Enter the elements of the array:\n";
    for (int i = 0; i < n; ++i) {
        cout << "Element " << i + 1 << ": ";
        cin >> arr[i];
    }
    return arr;
}
//Function to display results in a formatted manner
void displayResults(const vector<int>& arr, const vector<int>& surpasserCounts) {
    cout << "\nFinal Results:\n";
    cout << setw(10) << "Index" << setw(10) << "Element" << setw(20) << "Surpasser Count\n";
    cout << string(40, '-') << endl;
    for (int i = 0; i < arr.size(); ++i) {
        cout << setw(10) << i << setw(10) << arr[i] << setw(20) << surpasserCounts[i] << endl;
    }
}
// Main Function
int main() {
    cout << "      Surpasser Count Calculator\n";
    cout << "This program calculates the surpasser count\n";
    cout << "for each element in an array using the brute\n";
    cout << "force approach.\n";
    cout << "---------------------------------------------\n";
    // Step 1: Input the array
    vector<int> arr = inputArray();
    cout << "\nInput array successfully read!\n";
    // Step 2: Display the input array
    displayArray(arr, "Input Array");
    cout << "\nNow calculating surpasser counts...\n";
    // Step 3: Calculate surpasser counts
    vector<int> surpasserCounts = calculateSurpasserCounts(arr);
    cout << "\nSurpasser counts calculated successfully!\n";
    // Step 4: Display the results
    displayResults(arr, surpasserCounts);
    cout << "\nThank you for using the Surpasser Count Calculator!\n";
    return 0;
}

Output:

Output

Surpasser Count Calculator
This program calculates the surpasser count
for each element in an array using the brute
force approach.
---------------------------------------------
Enter the number of elements in the array: 5
Enter the elements of the array:
Element 1: 23
Element 2: 45
Element 3: 42
Element 4: 67
Element 5: 59
Input array successfully read!
Input Array:  23  45  42  67  59 
Now calculating surpasser counts...
Calculating surpasser counts step-by-step:
Element at index 0 (23):
  Comparing 23 with 45... Surpasser found!
  Comparing 23 with 42... Surpasser found!
  Comparing 23 with 67... Surpasser found!
  Comparing 23 with 59... Surpasser found!
  Total surpassers for 23: 4
Element at index 1 (45):
  Comparing 45 with 42... Not a surpasser.
  Comparing 45 with 67... Surpasser found!
  Comparing 45 with 59... Surpasser found!
  Total surpassers for 45: 2
Element at index 2 (42):
  Comparing 42 with 67... Surpasser found!
  Comparing 42 with 59... Surpasser found!
  Total surpassers for 42: 2
Element at index 3 (67):
  Comparing 67 with 59... Not a surpasser.
  Total surpassers for 67: 0
Element at index 4 (59):
  Total surpassers for 59: 0
Surpasser counts calculated successfully!
Final Results:
     Index   Element    Surpasser Count
----------------------------------------
         0        23                   4
         1        45                   2
         2        42                   2
         3        67                   0
         4        59                   0
Thank you for using the Surpasser Count Calculator!

Explanation:

  • displayArray Function: It is the function that takes an array as an argument and has to print the contents of the array. It takes two parameters: the array and a label. This library is used to select setw function to make sure it spruces up neatly and gives consistency between array elements.
  • calculateSurpasserCounts Function: It computes the surpasser count for each element. It does so by using two nested loops: In the outer loop (i loop), we iterate through each item in the array and consider each item to be the current item to check. The inner loop (j loop) compares the current element with all the other elements after it (i.e., higher-index elements). It counts the surpassers of arr[j] when an element in the inner loop (arr[j]) is greater than the element in the outer loop (arr[i], i.e., the element arrayed in the second subscript is greater than the corresponding element in the first subarray). At each step, it prints detailed information about comparisons.
  • inputArray Function: This Function takes the user input for the size of the array (n) and the elements themselves. It populates the array with the user input and returns that array.
  • displayResults Function: Finally, this function finds the surpasser counts and formats them, displaying the final results in a table. It maps the element, its index, and the number of surpasses of its neighboring elements.
  • main Function: The main function controls the flow of the program. First, it calls the inputArray function to get the input from the user. After that, it calls the displayArray function to show the array passed in. Finally, it calls calculateSurpasserCounts function to calculate the surpasser counts. Here, it reports to display results to print out the result of the number of samples surpassing each element.
  • Flow and Output: The program's first message is an introductory message. It asks the user for an array, and brute-force calculates the surpasser counts, then prints them into a nicely formatted view. The print statements give detailed clarity on how each element has been decided to have a surpasser count.
  • It is the function that takes an array as an argument and has to print the contents of the array. It takes two parameters: the array and a label.
  • This library is used to select setw function to make sure it spruces up neatly and gives consistency between array elements.
  • It computes the surpasser count for each element. It does so by using two nested loops: In the outer loop (i loop), we iterate through each item in the array and consider each item to be the current item to check. The inner loop (j loop) compares the current element with all the other elements after it (i.e., higher-index elements).
  • It counts the surpassers of arr[j] when an element in the inner loop (arr[j]) is greater than the element in the outer loop (arr[i], i.e., the element arrayed in the second subscript is greater than the corresponding element in the first subarray).
  • At each step, it prints detailed information about comparisons.
  • This Function takes the user input for the size of the array (n) and the elements themselves. It populates the array with the user input and returns that array.
  • Finally, this function finds the surpasser counts and formats them, displaying the final results in a table. It maps the element, its index, and the number of surpasses of its neighboring elements.
  • The main function controls the flow of the program. First, it calls the inputArray function to get the input from the user. After that, it calls the displayArray function to show the array passed in. Finally, it calls calculateSurpasserCounts function to calculate the surpasser counts.
  • Here, it reports to display results to print out the result of the number of samples surpassing each element.
  • The program's first message is an introductory message. It asks the user for an array, and brute-force calculates the surpasser counts, then prints them into a nicely formatted view. The print statements give detailed clarity on how each element has been decided to have a surpasser count.
  • Complexity Analysis:

Time Complexity:

The brute force method employed in the provided code can be examined by analyzing the iterations used to determine the count of surpasses for each element.

  • Outer Loop: The initial loop iterates through each element in the array once. This loop will iterate n times if the array contains n elements.
  • Inner Loop: Within the outer loop, the inner loop compares each element with every subsequent element. For each element i, the inner loop will have n-i-1 iterations. The maximum number of inner loop iterations occurs when i is 0, resulting in n-1 iterations. As i increases, the number of inner loop iterations decreases.

With this approach, the time complexity becomes O(n²) in terms of brute force. This quadratic complexity arises as each element undergoes a linear number of comparisons with all subsequent elements in the array.

Space Complexity:

Except for the input array, the extra space used determines the space complexity of the program.

  • Input Array: The extra space complexity doesn't count the input array (it is already part of the input).
  • Surpasser Count Array: We created a surpasserCount array to store the surpasser count for each element. Therefore, this array is the same size as the input array and occupies O(n) space.
  • Auxiliary Variables: The use of a few integer variables (loop counters, array indices, etc.) requires constant space (O(1)), though.

It can be deduced that the storage space required for the surpasserCount array directly influences the overall space complexity, resulting in O(n).

Approach-2: Fenwick Tree Approach

The Fenwick Tree, also referred to as the Binary Indexed Tree, serves as a data structure employed for executing operations on cumulative frequency tables. It enables operations like updates and queries to be completed in O(log n) time. When tackling the surpasser count problem, where the objective is to determine the number of elements larger than a specific element to its right, the Fenwick Tree proves to be invaluable in efficiently monitoring elements while traversing the array from right to left.

By employing a coordinate compression method, we adapt the array elements to align with the 1-based indexing system utilized by the Fenwick Tree. With this approach, we interrogate the tree for the count of larger elements encountered up to that point for each element, before incorporating the current element into the tree. This optimized strategy yields a solution with a time complexity of O(n log n), significantly outperforming brute-force approaches in terms of speed.

Example:

Let's consider an illustration to demonstrate the count of surpasser for each element within the array in C++ by implementing the Fenwick tree method.

Example

#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <string>
using namespace std;
// Fenwick Tree (Binary Indexed Tree) Class
class FenwickTree {
private:
    vector<int> fenwick;
    int n;    
public:
    // Constructor to initialize the Fenwick Tree with size n
    FenwickTree(int size) {
        n = size;
        fenwick.resize(n + 1, 0);  // Fenwick Tree is 1-indexed
    }    
    // Update the Fenwick Tree at a specific index
    void update(int index, int value) {
        while (index <= n) {
            fenwick[index] += value;  // Add value at index
            index += index & -index;  // Move to next index in the Fenwick Tree
        }
    }   
    // Query the prefix sum up to a specific index
    int query(int index) {
        int sum = 0;
        while (index > 0) {
            sum += fenwick[index];  // Add the value at index
            index -= index & -index;  // Move to the parent index
        }
        return sum;
    }
};
// Function for Coordinate Compression
unordered_map<int, int> coordinateCompress(const vector<int>& arr) {
    vector<int> sortedArr = arr;
    sort(sortedArr.begin(), sortedArr.end());
    sortedArr.erase(unique(sortedArr.begin(), sortedArr.end()), sortedArr.end());    
    unordered_map<int, int> compress;
    for (int i = 0; i < sortedArr.size(); ++i) {
        compress[sortedArr[i]] = i + 1;  // 1-based index for Fenwick Tree
    }    
    return compress;
}
//Function to count surpassers using Fenwick Tree
vector<int> countSurpassers(const vector<int>& arr) {
    int n = arr.size();
    vector<int> result(n, 0);
    // Step 1: Coordinate Compression for the array values
    unordered_map<int, int> compress = coordinateCompress(arr);
    // Step 2: Initialize Fenwick Tree for counting frequencies
    FenwickTree fenwick(compress.size()); 
    // Step 3: Traverse the array from right to left
    for (int i = n - 1; i >= 0; --i) {
        int val = arr[i];
        int compressedVal = compress[val];       
        // Step 4: Query the number of surpassers (elements larger than current value)
        result[i] = fenwick.query(compress.size()) - fenwick.query(compressedVal);
        
        // Step 5: Update the Fenwick Tree with the current element
        fenwick.update(compressedVal, 1);
    }
    return result;
}
//Function to display the array
void displayArray(const vector<int>& arr, const string& label) {
    cout << label << ": ";
    for (int val : arr) {
        cout << val << " ";
    }
    cout << endl;
}
//Function to display the results
void displayResults(const vector<int>& arr, const vector<int>& surpasserCounts) {
    cout << "\nFinal Results:\n";
    cout << "Element | Surpasser Count\n";
    cout << "-------------------------\n";
    for (int i = 0; i < arr.size(); ++i) {
        cout << arr[i] << "      | " << surpasserCounts[i] << endl;
    }
}
int main() 
{    
    // Step 1: Ask the user for the number of elements in the array
    int n;
    cout << "Enter the number of elements in the array: ";
    cin >> n;    
    // Step 2: Take array input from the user
    vector<int> arr(n);
    cout << "Enter the elements of the array: \n";
    for (int i = 0; i < n; ++i) {
        cout << "Element " << i + 1 << ": ";
        cin >> arr[i];
    }    
    // Step 3: Display input array
    displayArray(arr, "Input Array"); 
    // Step 4: Calculate surpasser counts
    vector<int> surpasserCounts = countSurpassers(arr); 
    // Step 5: Display the results
    displayResults(arr, surpasserCounts); 
    cout << "End of Program\n";
    return 0;
}

Output:

Output

Enter the number of elements in the array: 5
Enter the elements of the array: 
Element 1: 34
Element 2: 45
Element 3: 56
Element 4: 67
Element 5: 78
Input Array: 34 45 56 67 78 
Final Results:
Element | Surpasser Count
-------------------------
34      | 4
45      | 3
56      | 2
67      | 1
78      | 0
End of Program

Explanation:

  1. Implementation of Fenwick Tree (Binary Indexed Tree) Class:

The core of the solution lies in the FenwickTree class, which encompasses two fundamental operations:

  • update(index, value): This function modifies the value (either increments or decrements) at a specified index within the Fenwick Tree, subsequently adding this value to the existing Fenwick Tree structure. This update ripples through the tree, ensuring the cumulative frequency of elements is accurately maintained.
  • query(index): Utilized for calculating the cumulative sum of frequencies for all elements ranging from index 1 up to a specified index in the tree. Notably, this method adeptly determines the count of elements that are less than or equal to a given value.

Because of its structure, the Fenwick Tree is a perfect match for problems in which we need such fast cumulative sum updates and queries, with O (log n) on the update and query time.

  1. Coordinate Compression:
  • If the input array values might be large or sparse, coordinate compression is needed. However, we need Fenwick Trees mapped to a smaller contiguous range, so we map the array's values into a smaller range. It allows us to avoid large and sparse Fenwick Trees.
  • The coordinateCompress Function merges duplicate values, sorts the array with the duplicates removed, and compresses the existing elements to each unique element to a 1-based index. It enables us to use these compressed indices efficiently in the Fenwick Tree.
  1. Counting Surpassers:

The countSurpassers function determines the surpasser count for each item in the array using the following steps:

  • Traversing from right to left: The method scans the array from right to left. It accesses the Fenwick Tree for each item to ascertain the number of items that exceed it.
  • Fenwick Tree Modification: Subsequently, the function computes the surpasser count for the current item and then modifies the Fenwick Tree by storing the compressed index of the current item for later inquiries.

With this approach, we check an element exactly once and compute the result in O(log n) time per element. Thus, the entire running time is O(n log n).

  1. Main Function and User Interaction:
  • User Input: The main function reads according to the size of the array and each element. After that, it calls the countSurpassers function to compute the values of surpasser counts.
  • Displaying Results: The surpasser counts are computed with every element in the array along with its corresponding surpasser count, and then the results are displayed in a tabular representation.
  • Complexity Analysis:

Time Complexity:

Coordinate Compression:

This specific type of element within the array operates at a time complexity of O(n log n). By traversing the array and mapping each element's compressed index, we can achieve a time complexity of O(n) for this process. The sorting involved in coordinate compression results in an overall time complexity of O(n log n).

Fenwick Tree Operations:

For every item in the array, two primary actions are executed:

  • Query: This action calculates the quantity of surpassers (elements larger than the current element) by utilizing the Fenwick Tree query method. The Fenwick Tree efficiently handles queries in O(log n) time.
  • Update: Subsequently, the Fenwick Tree is queried and modified with the count of the current element. The updating process is also completed within O(log n) time.

Both actions (querying and updating) require O(log n) time for each individual element. Since both actions are executed on every single one of the n elements in the array, the overall time complexity for processing all elements amounts to O(n log n).

Space Complexity:

The space complexity of the Fenwick Tree-based solution can be analyzed by considering the memory used by the various components of the algorithm:

  • Input Array: The elements of arr make up n inputs. This requires O(n) space.
  • Coordinate Compression: We map the contents of the compressed indices to a map (or array), with each unique element mapped to its corresponding compressed index. At worst, the maximum number of unique elements is n, so the compressed index storage takes O(n) space.
  • Fenwick Tree (Binary Indexed Tree): The Fenwick Tree stores the cumulative frequency of each index. The Fenwick Tree array requires O(n) space because the size of the tree is determined by the range of the compressed indices at most n.
  • Temporary variables : The process uses a few more variables (as used in queries and updates) but only constant space O(1). Finally, the space complexity is the sum of space required for the input array, coordinate compression, and Fenwick Tree. All of these components can run in O(n) space because they don't overlap (abutting it into O(n²) space), which means that the final space complexity is O(n).

Input Required

This code uses input(). Please provide values below:

Logic Practice
Install Logic Practice
Add to home screen for a faster app-like experience