找到左侧较小元素的总和

Posted

技术标签:

【中文标题】找到左侧较小元素的总和【英文标题】:finding the sum of smaller elements on left 【发布时间】:2012-03-10 13:12:19 【问题描述】:

我遇到了一个问题,即在整数数组中查找每个元素左侧的较小元素的数量,这可以通过使用 二叉索引树在 O(nlgn) 中解决(如 AVL 等)或 合并排序。使用 AVL 树可以计算每个元素的左子树的大小,这将是必需的答案。但是我想不出如何有效地计算每个元素留下的较小元素的 sum。对于每个元素,我是否必须遍历左子树并对节点处的值求和,还是有更好的方法(使用合并排序等)? 例如对于数组:4,7,1,3,2 所需的答案是:0,4,0,1,1

谢谢。

【问题讨论】:

最后一个答案不应该是1吗?另外,您能否描述一下如何使用 AVL 树解决计数问题?我不清楚。 谢谢..更正。对于计数问题,在添加任何元素时,它会与 root 进行比较,如果小于则添加到左侧,否则添加到右侧。因此,对于任何节点,其左子树的大小都会给出比它小的元素数。 是的,这样您就可以计算比它小的项目数。但它们不仅限于左侧的项目。哦,也许现在我明白了:您在插入东西后立即数较小的项目,而不是在最后,对吧? 【参考方案1】:

二叉索引树中,您为二叉搜索树的每个节点存储子节点的数量。这使您可以找到每个节点之前的节点数(较小元素的数量)。

对于此任务,您可以为二叉搜索树的每个节点存储子节点值的总和。这使您可以找到前面节点的值的总和(较小元素的总和)。也在 O(n*log(n)) 中。

【讨论】:

【参考方案2】:

检查二叉索引树上的this tutorial。这是一个结构,它使用 O(n) 内存并且可以执行这样的任务: 1. 将 a[i] 的值更改为 (to) x,调用此 add(i,x); 2. 返回所有a[i], iget(x). 在 O(log n)

现在,如何将它用于您的任务。您可以分两步完成此操作。 第一步。从原始数组中复制、排序和删除重复项。现在您可以重新映射数字,使它们在 [1...n] 范围内。 步骤 2. 现在从左到右遍历阵列。设 A[i] - 原始数组中的值,new[i] - 映射值。 (如果 A = [2, 7, 11, -3, 7] 那么 new = [2, 3, 4, 1, 2])。

答案是get(new[i]-1)。

更新值:add(new[i], 1) 用于计数,add(new[i], A[i]) 用于求和。

总之。排序和重新映射是 O(n logn)。处理数组是 n * O(log n) = O(n log n)。所以总复杂度是 O(n logn)

或者,使用treap(俄语)。

编辑:构建新数组。 假设原始数组 A = [2, 7, 11,-3, 7] 将其复制到 B 并排序,B = [-3, 2, 7, 7, 11] 做一个独特的 B = [-3, 2, 7, 11]。 现在要获得新的,你可以

    将所有元素按升序添加到映射中,例如(-3 -> 1, 2->2, 7->3, 11->4) 对于 A 中的每个元素,对 B 进行二分查找

【讨论】:

对于最后一个'7'的位置是如何确定的?是(第一个'7'的位置)-1,如果有超过2个重复,是否会类似计算? 如果你想删除重复的数组从第二个元素移动到结束,支持重复计数。如果 B[i] == B[i-cnt-1] 将 cnt 增加 1。否则如果 cnt != 0,则交换 B[i] 和 B[i-cnt]。从 B 中删除最后一个 cnt 元素。基本上,您想要获取 相对 数字,而不是初始值以使用 BIT。 对于每个节点,都有其子树中的键的总和。当合并/拆分做 sum(parent) = sum(left) + sum(right) + value。然后当得到所有的总和时,小于 X 对 X 进行切片,返回左树的总和。如果优先级是随机的,这两个操作都在 O(4 * log N) 中工作,所以你有 O(log N) 的解决方案。 @kilotaras:可能是我错过了一些非常明显的东西,但这是我所理解的......从映射中,如果我们对每个 A 的元素进行二进制搜索:2-> 2, 7->3, 11->4, -3->1 和 7-> ?我们如何为 7 再次获得“2”,以便新数组为 2,3,4,1,2 new[i] 是 A[i] 在 B 中从 1 开始的位置。您可以通过 2 种方式找到它。有一个 map:value -> 新值。对于 A 中的每个数字,对 B 进行二分搜索,得到它的位置。【参考方案3】:

以下代码的复杂度为 O(nlogn)。 它使用二叉索引树来解决问题。

#include <cstdio>
using namespace std;

const int MX_RANGE = 100000, MX_SIZE  = 100000;
int tree[MX_RANGE] = 0, a[MX_SIZE];

int main() 
  int n, mn = MX_RANGE, shift = 0;
  scanf("%d", &n);

  for(int i = 0; i < n; i++) 
    scanf("%d", &a[i]);
    if(a[i] < mn) mn = a[i];  
  

  shift = 1-mn; // we need to remap all values to start from 1

  for(int i = 0; i < n; i++) 
    // Read answer
    int sum = 0, idx = a[i]+shift-1;
    while(idx>0) 
      sum += tree[idx];
      idx -= (idx&-idx);
     

    printf("%d ", sum);

    // Update tree
    idx = a[i]+shift;
    while(idx<=MX_RANGE) 
      tree[idx] += a[i];
      idx += (idx&-idx);
    
  

  printf("\n");

【讨论】:

以上是关于找到左侧较小元素的总和的主要内容,如果未能解决你的问题,请参考以下文章

如何从数组中找到精确的(最小)元素以匹配给定的总和

在数组中找到两个总和最小的非后续元素

有效地在矩阵中找到其元素总和为目标数的路径? [关闭]

在数据框中,找到列的每个元素的下一个较小值的索引

如何优化 C 代码:寻找下一个设置位并找到相应数组元素的总和

在 O(nlogk) 时间内找到长度为 n 的列表中总和小于 t 的 k 个元素