【问题标题】:Calculating number distinct elements in a huge/big array计算巨大/大数组中不同元素的数量
【发布时间】:2013-12-14 12:56:55
【问题描述】:

我有一个大数组说..数组[7000]。在每个索引处都有一个介于 0 和 9 之间的数字。然后给定两个索引说 x=100 , y= 105 我需要找到之间不同元素的数量给定的范围。 假设

array[100]=1;
array[101]=2;
array[102]=1;
array[103]=4;
array[104]=7;
array[105]=2;

现在这里不同元素的数量是 4 {即数字 1 ,2,1,7 }。 这可以通过在两个索引之间迭代并跟踪数字来计算。但我面临的主要问题是程序必须执行的查询数量非常大,即必须为几千个 x 和 y 值(比如 10^6 个查询),在这种情况下,这种方法将变得非常低效。有什么更好的算法,我尝试在很多地方搜索。

【问题讨论】:

  • 7000 个条目听起来并不多。你不能预先计算范围吗?或预先计算子范围。还是缓存计算已经完成?
  • 我更关心查询的数量,它们将是 10^6 范围,其中数组可以是 0 到 10000 之间的任何值。不超过 10000。
  • 如果您每次查询的数组都相同,那么您一定会根据您执行的查询次数多次执行相同的查询。因此预计算或缓存会产生影响。
  • 数组每次都是一样的。
  • @EbbeM.Pedersen 7000^2 约为 10^7,因此每个查询都可以是唯一的

标签: performance algorithm data-structures


【解决方案1】:

好的,这个解决方案的内存效率不高,但它可以用于计算 O(1) 中的查询。

形成另一个 7000x10 的二维数组,其中每一行是该条目之前的 [0 到 9s] 的计数。

因此,每当您收到查询时,只需减去行的相应索引即可获得 [0..9] 个数字的计数。

检查哪些索引的计数差异非零。

例如,数组行可能如下所示:

A[36] = { 5, 4, 2,  1, 4, 3, 7, 8, 2, 1 }
A[38] = { 6, 4, 2,  1, 4, 3, 7, 8, 2, 2 }

Diff =  { 1, 0, 0,  0, 0, 0, 0, 0, 0, 1 }

所以在 36 和 38 之间有两个唯一元素(0 和 9)。

示例 2:
对于输入数组 {1, 2, 3, 2, 4},矩阵 A 应为:

A[0] = { 0, 1, 0,  0, 0, 0, 0, 0, 0, 0 }
A[1] = { 0, 1, 1,  0, 0, 0, 0, 0, 0, 0 }
A[2] = { 0, 1, 1,  1, 0, 0, 0, 0, 0, 0 }
A[3] = { 0, 1, 2,  1, 0, 0, 0, 0, 0, 0 }
A[4] = { 0, 1, 2,  1, 1, 0, 0, 0, 0, 0 }

所以对于 A[3] 到 A[4],应该有 1 个唯一元素 (4)

Diff =  { 0, 0, 0,  0, 1, 0, 0, 0, 0, 0 }

所以对于 A[1] 到 A[4],应该有 3 个唯一元素 (2, 3, 4)

Diff =  { 0, 0, 1,  1, 1, 0, 0, 0, 0, 0 }

编辑:我考虑了 A[1] 到 A[4] 表示索引 [2 到 4] 中的唯一元素的约定。如果需要,可以将其更改为 1 到 4。在这种情况下,

Diff =  { 0, 0, 2,  1, 1, 0, 0, 0, 0, 0 }

(又是 3 个独特的元素)

【讨论】:

  • 你能举个例子吗?
  • @chill 示例已添加。
  • 我认为这行不通。这个数组{1, 2, 3, 2, 4} 应该是一个反例。例如。检查范围[3, 4](两个不同的元素)和[1, 4](三个不同的元素)。
  • 啊,对不起,我最初误解了算法,没关系。
  • 考虑 A[0] 和 A[4] 的情况......然后你觉得需要做一点改变
【解决方案2】:

您正在寻找分段的树。

http://en.wikipedia.org/wiki/Segment_tree

从每个索引的值在 0-1 之间的问题开始,然后逐步向上。您可以使用 O(logn) 时间回答然后查询并使用 O(nlogn) 时间构建树。

【讨论】:

    【解决方案3】:

    由于您的瓶颈是查询时间,并且数组元素是从 0 到 9,这是我的解决方案,只需 O(n)。

    Assume:
    A = arr
    P = {} (Hash or Dict)
    P[A,0,-1] = [0,0,0,0,0,0,0,0,0,0]   
    
    For i = 0,len(A):
        e = A[i]
        P[A,0,i] = P[A,0,i-1] where P[A,0,i-1][e]++ 
    
    Query(A,x,y)
        res = 0
        ax = P[A,0,x]
        ay = P[A,0,y]
        for i = 0,10
            res += ay[i] - ax[i] > 1 ? 1 : ay[i] - ax[i]    
    
        return res + 1
    

    A = [1,2,1,4,7,2,2]

    P[A,0,0] = [0,1,0,0,0,0,0,0,0,0]

    P[A,0,1] = [0,1,1,0,0,0,0,0,0,0]

    P[A,0,2] = [0,2,1,0,0,0,0,0,0,0]

    P[A,0,3] = [0,2,1,0,1,0,0,0,0,0]

    P[A,0,4] = [0,2,1,0,1,0,0,1,0,0]

    P[A,0,5] = [0,2,2,0,1,0,0,1,0,0]

    P[A,0,6] = [0,2,3,0,0,0,0,0,0,0]

    查询(A,1,4) = [0,1,0,1,0,0,0,0,1,0,0] = 3+1 = 4

    查询(A,4,6) = [0,0,2,0,0,0,0,0,0,0,0] = [0,0,1,0,0,0,0 ,0,0,0,0] = 1+1 = 2

    【讨论】:

      【解决方案4】:

      由于数字在 0-9 范围内,我们可以使用一些不错的作弊方法。

      主要思想:使用整数中的i位来表示数字i的存在。所以 { } 变为 0,{ 0 } 变为 1,{ 1 } 变为 2,{ 0, 1 } 变为 3,以此类推。向其中添加一个项目x 很容易,只需使用set | (1 << x),同样,两个这样的集合的并集是微不足道的:set1 | set2。还有一种很好的方法来计算整数中 1 的位数,它给出了集合的基数。

      这还没有太大帮助,它只是提高了查询的实际性能。但这还不是全部。

      现在构建一棵二叉树,在叶子处以(1 << array[i]) | (1 << array[i + 1]) 作为节点。上面的级别,你取两个孩子的并集(如果一个孩子不存在,就取另一个孩子的值)。这棵树是一棵完整的二叉树,所以你应该像binary heap 一样存储它(当然,它不是堆)以避免在子指针上浪费大量空间。这样,您只需将表示集合的整数存储在不长于输入的数组中。构建这棵树显然需要 O(n) 时间。

      对于x .. y 范围的查询,取该范围内所有内容的并集*(注意边缘情况),然后计算最终集合的population count(可以稍微优化一下,因为只有 10 位可以设置)。这是一个 O(log(y - x)) 操作。

      *:稍微扩展一下,想法是向下递归树,并且只要范围完全包含当前节点,就返回该节点。如果叶子没有完全包含在范围内,则取
      1 << array[something](来自输入数组)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-01-15
        • 1970-01-01
        • 2013-11-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-17
        相关资源
        最近更新 更多