【问题标题】:How can we calculate, for every element in an array, the number of elements to the right that are greater than that element?对于数组中的每个元素,我们如何计算右侧大于该元素的元素数量?
【发布时间】:2021-05-21 12:34:05
【问题描述】:

给定一个具有 n 个值的数组 A,让 A 的 X 是一个数组,它在索引 i 中保存大于 A[i] 并且位于原始数组 @987654324 右侧的元素的数量@。

例如,如果 A 是:[10,12,8,17,3,24,19],那么 X(A) 是:[4,3,3,2,2,0,0]

如何在O(n log(n)) 时间和O(n) 空间复杂度下解决这个问题?

我可以通过使用循环在O(n^2) Time 和O(1) Space 中轻松解决这个问题,并且对于每个元素,计算右侧有多少元素比它大,但我没有成功满足这些要求.

我正在考虑使用快速排序,最坏的情况是可以在 O(n log(n)) 中完成,但我看不出排序数组在这里有什么帮助。

注意:关于快速排序,算法需要进行一些调整,以确保在最坏情况下而不是平均情况下的 O(n log(n))。

【问题讨论】:

    标签: c++ arrays c algorithm big-o


    【解决方案1】:

    问题陈述的快速总结:给定一个包含N 整数的数组A,构造一个数组X,使得对于每个iX[i] =A 中具有大于i 并且也大于A[i] 的索引。

    解决此问题的一种方法是使用二叉搜索树。首先从最后一个元素迭代到第一个元素,在我们迭代时将每个元素添加到集合中。每次我们在一个元素e处,使用二叉搜索树的find()操作来查找当前树中有多少元素大于e

    也许您的第一个想法是使用std::multiset(不是std::set,因为我们可能有重复的元素!),它是一个自平衡二叉搜索树,提供O(logN) 插入和O(logN) 元素查找.这似乎适用于该算法,但实际上不会。原因是当你调用std::multiset::find() 时,它会返回一个指向集合中元素的迭代器。查找集合中有多少元素实际上大于元素将花费O(N) 时间,因为查找从迭代器到集合末尾的距离需要重复递增。

    为了解决这个问题,我们使用了“indexed multiset”,它是一个稍微修改过的二叉搜索树,这样我们可以在O(logN)时间找到multiset中元素的index,而仍然支持O(logN) 插入。这是我演示此数据结构的代码:

    #include <iostream>
    #include <vector>
    #include <ext/pb_ds/assoc_container.hpp>
    
    using namespace std;
    using namespace __gnu_pbds;
    
    // I know this is kind of messy, but it's the general way to get a C++ indexed
    // multiset without using an external library
    typedef tree <int, null_type, less_equal <int>, rb_tree_tag,
    tree_order_statistics_node_update> indexed_set;
    
    int main()
    {
        int A_size;
        cin >> A_size;
    
        vector <int> A(A_size);
        for(int i = 0; i < A_size; ++i){
            cin >> A[i];
        }
        // Input Done
    
        indexed_set nums;
        vector <int> X(A_size);
        for(int i = A_size - 1; i >= 0; --i){
            // order_of_key returns the first index that A[i] would be at in a sorted list
            // with the same elements as nums.
            X[i] = nums.size() - nums.order_of_key(A[i]);
    
            nums.insert(A[i]);
        }
    
        for(int item : X){
            cout << item << " ";
        }
        cout << "\n";
    
        return 0;
    }
    
    

    因此,总体而言,一般策略是

    1. 从最后一个元素迭代到第一个元素。
    2. 对于每个元素,检查nums 以查看有多少元素大于当前元素。 (O(logN))
    3. 然后,插入当前元素并继续迭代。 (O(logN)) 显然,该算法的总时间复杂度为O(NlogN),空间复杂度为O(N)

    对该方法的观察和见解的快速总结:

    1. INSIGHT:如果我们从最后一个元素迭代到第一个元素(不是从第一个元素到最后一个元素),索引集将只包含在任何给定迭代中当前元素右侧的元素,这正是我们想要的.这节省了我们的时间,因为如果我们要从左到右迭代,我们不必担心在开始时插入所有元素然后一个一个地删除它们。

    2. 观察:std::set 不足以用于该算法中的二叉搜索树,因为尽管它提供了O(logN) 寻找一个元素,计算元素位置在集合中需要O(N)时间的最坏情况。然而,索引集在O(logN) 时间提供这种“定位”操作,以及插入。

    【讨论】:

      【解决方案2】:

      Telescope 首次提到(在 cmets 中)您可以使用二叉树来实现这一点。但是,您也可以使用以下替代方法:

      1. 使用 AVL 树;
      2. 每个节点都应该在其右子树上存储元素和元素的数量;
      3. 从头到尾迭代数组;
      4. 添加到树并相应地更新节点的大小。
      5. 添加时将当前元素与根元素进行比较;如果该元素小于根,则它小于右子树的所有元素。在这种情况下,从该节点获取大小,然后继续到左子树并应用相同的逻辑。将最终大小添加到数组X上的对应位置;
      6. 如果它不小于根,则增加根的大小并继续到适当的子树。并应用上述逻辑。

      插入树的时间复杂度将是 N 次。因此,O(n log(n))。而空间复杂度自然会是O(N)


      可视化:

      A : [10,12,8,17,3,24,19];
      X(A) [? ,? ,? ,? ,? ,? ,?]
      右树节点大小:S [?,?,?,?,?,?,?]

      插入 19:

      因此右子树中没有元素:

      • 19 的大小 = 0;
      • X(A) [? ,? ,? ,? ,? ,? ,0]
      • S [?, ?, ?, ?, ?, ?, 0]

      插入 24:

      • 24 大于根( 19)所以让我们增加根的大小并继续到右下树。
      • 24 的大小 = 0
      • X(A) [? ,? ,? ,? ,? ,0 ,0]
      • S [?, ?, ?, ?, ?, 0, 1]

      插入 3:

      • 3 小于根( 19)并且根的大小为 1,因此有 2 个元素大于根及其右子树的 3。让我们向左走;
      • 大小 3 = 0
      • X(A) [? ,? ,? ,? ,2 ,0 ,0]
      • S [? , ?, ?, ?, 0, 0, 1]

      插入 17:

      • 17 小于根( 19)并且根的大小为 1,因此有 2 个元素大于 17 的根及其右子树。让我们往左边走,17比根大(ie, 3),让我们把节点3的大小从0增加到1,然后到右边的子树。李>
      • 17 的大小 = 0
      • X(A) [? ,? ,? ,2 ,2 ,0 ,0]
      • S [? ,? ,? ,0 ,1 ,0 ,1]

      插入 8:

      • 8 小于根( 19)并且根的大小为 1,因此有 2 个元素大于根及其右子树的 8。让我们向左走,8 比根大(i.e., 3),让我们将节点 3 的大小从 1 增加到 2,然后到右子树。 8 也小于根 (i.e., 17),因此到目前为止 8 小于三个元素。让我们向左走。
      • 8 的大小 = 0
      • X(A) [? ,? ,3 ,2 ,2 ,0 ,0]
      • S [? ,? ,0 ,0 ,2 ,0 ,1]

      随着节点 8 的插入,执行旋转以平衡树。

      在旋转的过程中,大小也会更新,即节点 8 的大小由 0 变为 1,节点 3 的大小由 2 变为 0。: - S [? ,? ,1 ,0 ,0 ,0 ,1]

      插入 12:

      • 12 小于根( 19)并且根的大小为 1,因此有 2 个元素大于根及其右子树的 12。让我们向左走,12 比根大(i.e., 8),让我们将节点 8 的大小从 1 增加到 2,然后到右子树。 12 也小于根( 17),因此到目前为止 12 小于三个元素。让我们向左走。

      • 12 的大小 = 0

      • X(A) [? ,3 ,3 ,2 ,2 ,0 ,0]

      • S [? ,0 ,0 ,0 ,2 ,0 ,1]

      随着节点 12 的插入,执行双重旋转以平衡树。

      在旋转过程中,尺寸也会更新 - S [? ,0 ,1 ,2 ,0 ,0 ,1]

      插入 10:

      • 10 小于根( 17)并且根的大小为 2,因此有 3 个元素大于根及其右子树的 10。让我们向左走,10 比根大(i.e., 8),让我们将节点 8 的大小从 1 增加到 2,然后到右子树。 10 也小于根( 12),因此到目前为止 10 小于 4 个元素。让我们向左走。

      • 10 的大小 = 0
      • X(A) [4 ,3 ,3 ,2 ,2 ,0 ,0]
      • S [0 ,0 ,0 ,0 ,2 ,0 ,1]

      一个可能的 C 实现(AVL 代码改编自 source):

      #include<stdio.h>
      #include<stdlib.h>
       
      struct Node{
          int key;
          struct Node *left;
          struct Node *right;
          int height;
          int size;
      };
       
      int height(struct Node *N){
          return (N == NULL) ? 0 : N->height;
      }
      
      int sizeRightTree(struct Node *N){
          return (N == NULL || N -> right == NULL) ? 0 : N->right->height;
      }
       
      int max(int a, int b){
          return (a > b) ? a : b;
      }
       
      struct Node* newNode(int key){
          struct Node* node = (struct Node*) malloc(sizeof(struct Node));
          node->key   = key;
          node->left   = NULL;
          node->right  = NULL;
          node->height = 1;
          node->size = 0;
          return(node);
      }
       
      struct Node *rightRotate(struct Node *y) {
          struct Node *x = y->left;
          struct Node *T2 = x->right;
       
          x->right = y;
          y->left = T2;
       
          y->height = max(height(y->left), height(y->right))+1;
          x->height = max(height(x->left), height(x->right))+1;
          y->size = sizeRightTree(y);
          x->size = sizeRightTree(x);
          return x;
      }
       
      struct Node *leftRotate(struct Node *x){
          struct Node *y = x->right;
          struct Node *T2 = y->left;
       
          y->left = x;
          x->right = T2;
       
          x->height = max(height(x->left), height(x->right))+1;
          y->height = max(height(y->left), height(y->right))+1;
          y->size = sizeRightTree(y);
          x->size = sizeRightTree(x); 
      
          return y;
      }
       
      int getBalance(struct Node *N){
          return (N == NULL) ? 0 : height(N->left) - height(N->right);
      }
       
      struct Node* insert(struct Node* node, int key, int *size){
          if (node == NULL)
              return(newNode(key));
          if (key < node->key){
              *size = *size + node->size + 1;
              node->left  = insert(node->left, key, size);
          } 
          else if (key > node->key){
          node->size++;
          node->right = insert(node->right, key, size);
          }
          else 
              return node;
       
          node->height = 1 + max(height(node->left), height(node->right));
          int balance = getBalance(node);
       
          if (balance > 1 && key < node->left->key)
              return rightRotate(node);
          if (balance < -1 && key > node->right->key)
              return leftRotate(node);
          if (balance > 1 && key > node->left->key){
              node->left =  leftRotate(node->left);
              return rightRotate(node);
          }
          if (balance < -1 && key < node->right->key){
              node->right = rightRotate(node->right);
              return leftRotate(node);
          } 
          return node;
      }
      
      int main()
      {
        int arraySize = 7;
        struct Node *root = NULL;
        int A[7] = {10,12,8,17,3,24,19};
        int X[7] ={0};
        for(int i = arraySize - 1; i >= 0; i--)
           root = insert(root, A[i], &X[i]);
      
        for(int i = 0; i < arraySize; i++)
           printf("%d ", X[i]);
        printf("\n");
        return 0;
      }
      

      输出:

      4 3 3 2 2 0 0 
      

      【讨论】:

        【解决方案3】:

        类似于合并排序的东西,在处理右侧和处理左侧之前插入计数,例如:

        #include <algorithm>
        #include <functional>
        
        void count_greater_on_right( int* a, int* x, int begin, int end )
        {
            if( end - begin <= 2 )
            {
                if( end - begin == 2 && a[begin] < a[begin+1] )
                {
                    x[begin]+=1; // specific
                    std::swap( a[begin], a[begin+1] );
                }
                return;
            }
        
            int middle =(begin+end+1)/2;
            count_greater_on_right( a, x, middle, end );
        
            // specific
            {
                for( int i=begin; i!=middle; ++i )
                {
                    x[i]+=std::lower_bound( &a[middle], &a[end], a[i], std::greater<int>() )-&a[middle];
                }
            }
        
            count_greater_on_right( a, x, begin, middle );
            std::inplace_merge( &a[begin], &a[middle], &a[end], std::greater<int>() );
        }
        

        特定于任务的代码用 // specific; 排序的相反顺序使它稍微简单恕我直言; 更新“a”,因此如果您需要原始序列,请创建副本。

        【讨论】:

          【解决方案4】:

          如果将数组放入子范围然后对这些子范围进行排序,则可以解决问题。让我们详细看看,

          给定数组 = [10, 12, 8, 17, 3, 24, 19]
          现在将数组划分为长度为4 的子范围,并对这些子范围进行排序,如下所示,
          子范围排序数组

          ....................  ...............
          | 8 | 10 | 12 | 17 |  | 3 | 19 | 24 |
          ....................  ...............
            2    0    1    3      4    6    5    => index
          

          让我们获取子范围排序数组的第一个条目8,并尝试找到大于8的右元素数
          正如您在上面的数字8 中看到的那样,属于第一个子范围,并且由于子范围已排序,子范围中的元素按升序排列,但不按索引顺序排列。这意味着在当前子范围内,我们必须将元素8右侧的所有元素的索引与元素8的索引进行比较

          8 的索引是210index = 0,这意味着10 在输入数组中位于8 的左侧,
          12 的索引也小于8 的索引,这意味着12 在输入数组中位于8 的左侧,
          17 的索引是3 大于8 的索引,这意味着17 在输入数组中位于8 的右侧,可以认为是更大的元素,
          在将8的索引与当前子范围所有右元素的索引进行比较后,右更大的元素count = 1,让我们看看下一个范围,

          8的子范围之后,事情完全改变了,现在我们知道这个子范围在子范围元素8所属的右边,这意味着我们不必比较8的索引有元素或这个范围,都在元素8的右边,我们只需要找到大于8的数量,

          现在我们将右子范围的第一个元素与8 进行比较,正如您在上面看到的,第一个元素是3,它小于8,但如果右子范围的第一个元素大于当前元素然后我们可以直接将 count 增加到右子范围中存在的元素数。

          因为第一个元素3 小于8,我们在右子范围内找到8 的上限,即19,并且右子范围内19 中的所有元素都大于@987654359 @,所以有两个元素19, 24,由于这个计数增加了two,变成count = 3
          最后有3右元素大于元素8

          以类似的方式,可以为所有元素找到更大的右元素数量,结果数组将是,
          x(A) = [4, 3, 3, 2, 2, 0, 0]

          结论是,通过将输入数组划分为已排序的子范围,可以通过以下步骤找到右侧更大的元素,

          1. 比较当前子范围所有正确元素的索引,
          2. 比较右子范围的第一个元素和如果,
            一世。第一个元素大于当前元素,而不是右侧范围内的所有元素都大于当前元素,
            ii.第一个元素小于,然后找到右子范围内当前元素的上界,右子范围内上界的元素大于当前元素。
          3. 对所有正确的子范围重复第 2 步。
          #include <iostream>
          
          #include <vector>
          #include <iterator>
          #include <algorithm>
          
          using std::cout;
          
          std::vector<std::pair<int, std::size_t>> arrayOfSortedSubRange(std::size_t subRangeSize,
                                                                        const std::vector<int>& numArr){
          
          
              std::vector<std::pair<int, std::size_t>> res;
              res.reserve(numArr.size());
          
              for(std::size_t i = 0, numArrSize = numArr.size(); i < numArrSize; ++i){
          
                  res.emplace_back(numArr[i], i);
              }
          
              for(std::vector<std::pair<int, std::size_t>>::iterator it = res.begin(), endIt = res.end(); endIt != it;){
          
                  std::vector<std::pair<int, std::size_t>>::iterator rangeEndIt = it + std::min<std::ptrdiff_t>(endIt - it,
                                                                                                                subRangeSize);
          
                  std::sort(it, rangeEndIt, [](const std::pair<int, std::size_t>& a, const std::pair<int, std::size_t>& b){
                      return a.first < b.first;});
                  it = rangeEndIt;
              }
          
              return res;
          }
          
          std::size_t rightGreterElmentCountOfNumber(int num, std::vector<std::pair<int, std::size_t>>::const_iterator rightSubRangeIt,
                                        std::vector<std::pair<int, std::size_t>>::const_iterator endIt){
          
              std::size_t count = 0;
          
              std::vector<std::pair<int, std::size_t>>::const_iterator subRangEndIt = rightSubRangeIt +
                      std::min<std::ptrdiff_t>(endIt - rightSubRangeIt, 4);
          
              while(endIt != rightSubRangeIt){
          
                  if(rightSubRangeIt->first > num){
          
                      count += subRangEndIt - rightSubRangeIt;
                  }
                  else{
                      count += subRangEndIt -
                              std::upper_bound(rightSubRangeIt, subRangEndIt, num, [](int num,
                                               const std::pair<int, std::size_t>& element){ return num < element.first;});
                  }
          
                  rightSubRangeIt = subRangEndIt;
                  subRangEndIt += std::min<std::ptrdiff_t>(endIt - subRangEndIt, 4);
              }
          
              return count;
          }
          
          std::vector<std::size_t> rightGreaterElementCountForLessThanFiveNumbers(const std::vector<int>& numArr){
          
              std::vector<std::size_t> res(numArr.size(), 0);
              std::vector<std::size_t>::iterator resIt = res.begin();
          
              for(std::vector<int>::const_iterator it = numArr.cbegin(), lastIt = it + (numArr.size() - 1); lastIt != it;
                  ++it, ++resIt){
          
                  *resIt = std::count_if(it + 1, numArr.cend(), [num = *it](int rightNum){return rightNum > num;});
              }
          
              return res;
          }
          
          std::vector<std::size_t> rightGreaterElementCount(const std::vector<int>& numArr){
          
              if(numArr.size() < 5){
          
                  return rightGreaterElementCountForLessThanFiveNumbers(numArr);
              }
          
              std::vector<std::size_t> resArr(numArr.size(), 0);
              std::vector<std::pair<int, std::size_t>> subRangeSortedArr = arrayOfSortedSubRange(4, numArr);
          
              for(std::vector<std::pair<int, std::size_t>>::const_iterator it = subRangeSortedArr.cbegin(),
                  endIt = subRangeSortedArr.cend(); endIt != it;){
          
                  std::vector<std::pair<int, std::size_t>>::const_iterator rightNextSubRangeIt = it + std::min<std::ptrdiff_t>(
                              endIt - it, 4);
          
                  for(std::vector<std::pair<int, std::size_t>>::const_iterator eleIt = it; rightNextSubRangeIt != eleIt; ++eleIt){
          
                      std::size_t count = std::count_if(eleIt, rightNextSubRangeIt, [index = eleIt->second](
                                                        const std::pair<int, std::size_t>& element){ return index < element.second;});
          
                      if(endIt != rightNextSubRangeIt){
          
                          count += rightGreterElmentCountOfNumber(eleIt->first, rightNextSubRangeIt, endIt);
                      }
          
                      resArr[eleIt->second] = count;
                  }
          
                  it += std::min<std::ptrdiff_t>(endIt - it, 4);
              }
          
              return resArr;
          }
          
          int main(){
          
              std::vector<std::size_t> res = rightGreaterElementCount({10, 12, 8, 17, 3, 24, 19});
          
              cout<< "[10, 12, 8, 17, 3, 24, 19] => [";
          
              std::copy(res.cbegin(), res.cbegin() + (res.size() - 1), std::ostream_iterator<std::size_t>(cout, ", "));
          
              cout<< res.back()<< "]\n";
          }
          

          输出:
          [10, 12, 8, 17, 3, 24, 19] =&gt; [4, 3, 3, 2, 2, 0, 0]

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-06-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-03-18
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多