Search code examples
c++algorithmtime-complexity

How many number are less than or equal than x in an array?


Given an integer n and array a, I need to find for each i, 1≤ in, how many elements on the left are less than or equal to ai

Example:

    5 
    1 2 1 1 2

Output

    0 1 1 2 4

I can do it in O(N2) but I want to ask if there is any way to do it faster, since N is very large (N ≤ 106)?


Solution

  • I like to consider a bigger array to explain, so let's consider following array,

    2, 1, 3, 4, 7, 6, 5, 8, 9, 10, 12, 0, 11, 13, 8, 9, 12, 20, 30, 60

    The naïve way is to compare an element with all elements at left of it. Naïve approach has complexity of O(n^2) which make it not useful for big array.

    If you look this problem closely you will find a pattern in it, and the pattern is Rather than comparing with each left element of an element we can compare first and last value of a range!. Wait a minute what is the range here?

    These numbers can be viewed as ranges and there ranges can be created from traversing left to right in array. Ranges are as follows,

    [2], [1, 3, 4, 7], [6], [5, 8, 9, 10, 12], [0, 11, 13], [8, 9, 12, 20, 30, 60]

    Let’s start traversing array from left to right and see how we can create these ranges and how these ranges shall reduce the effort to find all small or equal elements at left of an element.

    Index 0 have no element at its left to compare thus why we start form index 1, at this point we don’t have any range. Now we compare value of index 1 and index 0. Value 1 is not less than or equals to 2, so this is very import comparison, due to this comparison we know the previous range should end here because now numbers are not in acceding order and at this point we get first range [2], which contains only single element and number of elements less than or equals to left of element at index 1 is zero.

    As continue with traversing left to right at index 2 we compare it with previous element which is at index 1 now value 1 <= 3 it means a new range is not staring here and we are still in same range which started at index 1. So to find how many elements less than or equals, we have to calculate first how many elements in current range [1, 3), in this case only one element and we have only one know range [2] at this point and it has one element which is less than 3 so total number of less than or equals elements at the left of element at index 2 is = 1 + 1 = 2. This can be done in similar way for rest of elements and I would like to jump directly at index 6 which is number 5,

    At index 6, we have all ready discovered three ranges [2], [1, 3, 4, 7], [6] but only two ranges [2] and [1, 3, 4, 7] shall be considered. How I know in advance that range [6] is not useful without comparing will be explained at the end of this explanation. To find number of less than or equals elements at left, we can see first range [2] have only one element and it is less than 5, second range have first element 1 which is less than 5 but last element is 7 and it is greater than 5, so we cannot consider all elements of range rather we have to find upper bound in this range to find how many elements we can consider and upper bound can be found by binary search because range is sorted , so this range contains three elements 1, 3, 4 which are less then or equals to 5. Total number of elements less than or equals to 5 from two ranges is 4 and index 6 is first element of current range and there is no element at left of it in current range so total count = 1 + 3 + 0 = 4.

    Last point on this explanation is, we have to store ranges in tree structure with their first value as key and value of the node should be array of pair of first and last index of range. I will use here std::map. This tree structure is required so that we can find all the range having first element less than or equals to our current element in logarithmic time by finding upper bound. That is the reason, I knew in advance when I was comparing element at index 6 that all three ranges known that time are not considerable and only two of them are considerable .
    Complexity of solution is,

    1. O(n) to travels from left to right in array, plus
    2. O(n (m + log m)) for finding upper bound in std::map for each element and comparing last value of m ranges, here m is number of ranges know at particular time, plus
    3. O(log q) for finding upper bound in a range if rage last element is greater than number, here q is number of element in particular range (It may or may not requires)
    #include <iostream>
    
    #include <map>
    #include <vector>
    
    #include <iterator>
    #include <algorithm>
    
    unsigned lessThanOrEqualCountFromRage(int num, const std::vector<int>& numList,
                                          const std::map<int,
                                          std::vector<std::pair<int, int>>>& rangeMap){
    
        using const_iter = std::map<int, std::vector<std::pair<int, int>>>::const_iterator;
    
        unsigned count = 0;
        const_iter upperBoundIt = rangeMap.upper_bound(num);
    
        for(const_iter it = rangeMap.cbegin(); upperBoundIt != it; ++it){
    
            for(const std::pair<int, int>& range : it->second){
    
                if(numList[range.second] <= num){
                    count += (range.second - range.first) + 1;
                }
                else{
                    auto rangeIt = numList.cbegin() + range.first;
    
                    count += std::upper_bound(rangeIt, numList.cbegin() +
                                              range.second, num) - rangeIt;
                }
            }
        }
    
        return count;
    }
    
    
    std::vector<unsigned> lessThanOrEqualCount(const std::vector<int>& numList){
    
        std::vector<unsigned> leftCountList;
        leftCountList.reserve(numList.size());
    
        leftCountList.push_back(0);
    
        std::map<int, std::vector<std::pair<int, int>>> rangeMap;
    
        std::vector<int>::const_iterator rangeFirstIt = numList.cbegin();
    
        for(std::vector<int>::const_iterator it = rangeFirstIt + 1, endIt = numList.cend();
            endIt != it;){
    
            std::vector<int>::const_iterator preIt = rangeFirstIt;
    
            while(endIt != it && *preIt <= *it){
    
                leftCountList.push_back((it - rangeFirstIt) +
                                        lessThanOrEqualCountFromRage(*it,
                                                                     numList, rangeMap));
    
                ++preIt;
                ++it;
            }
    
            if(endIt != it){
    
               int rangeFirstIndex = rangeFirstIt - numList.cbegin();
               int rangeLastIndex = preIt - numList.cbegin();
    
               std::map<int, std::vector<std::pair<int, int>>>::iterator rangeEntryIt =
                       rangeMap.find(*rangeFirstIt);
    
               if(rangeMap.end() != rangeEntryIt){
    
                   rangeEntryIt->second.emplace_back(rangeFirstIndex, rangeLastIndex);
               }
               else{
    
                   rangeMap.emplace(*rangeFirstIt, std::vector<std::pair<int, int>>{
                                        {rangeFirstIndex,rangeLastIndex}});
               }
    
               leftCountList.push_back(lessThanOrEqualCountFromRage(*it, numList,
                                                                    rangeMap));
    
               rangeFirstIt = it;
               ++it;
            }
        }
    
        return leftCountList;
    }
    
    int main(int , char *[]){
    
        std::vector<int> numList{2, 1, 3, 4, 7, 6, 5, 8, 9, 10, 12,
                                 0, 11, 13, 8, 9, 12, 20, 30, 60};
    
        std::vector<unsigned> countList = lessThanOrEqualCount(numList);
    
        std::copy(countList.cbegin(), countList.cend(),
                  std::ostream_iterator<unsigned>(std::cout, ", "));
    
        std::cout<< '\n';
    }
    

    Output:
    0, 0, 2, 3, 4, 4, 4, 7, 8, 9, 10, 0, 11, 13, 9, 11, 15, 17, 18, 19,