根据查询计数

时间:2015-08-08 07:29:22

标签: arrays algorithm

给出一组N个正元素。让我们假设我们列出阵列A的所有N×(N + 1)/ 2个非空连续子阵列,然后用相应子阵列中存在的最大元素替换所有子阵列。所以现在我们有N×(N + 1)/ 2个元素,其中每个元素在其子阵列中是最大的。

现在我们正在进行Q查询,其中每个查询都是以下三种类型之一:

1 K:我们需要计算N×(N + 1)/ 2个元素中严格大于K的数字。

2 K:我们需要在N×(N + 1)/ 2个元素中计算严格小于K的数字。

3 K:我们需要计算N×(N + 1)/ 2个元素中等于K的数字。

现在面临的主要问题是N可以达到10 ^ 6。所以我无法生成所有那些N×(N + 1)/ 2个元素。请帮助解决这个问题。

示例:设N = 3,我们有Q = 2。令数组A为[1,2,3],则所有子数组为:

[1] -> [1]
[2] -> [2]
[3] -> [3]
[1,2] -> [2]
[2,3] -> [3]
[1,2,3] -> [3]

所以现在我们有[1,2,3,2,3,3]。因为Q = 2所以:

Query 1 : 3 3

这意味着我们需要判断数字的数量等于3.所以答案是3,因为在生成的数组中有3个数字等于3。

Query 2 : 1 4

这意味着我们需要告诉大于4的数字。所以答案是0,因为生成的数组中没有人大于4。

现在N和Q都可以达到10 ^ 6。那么如何解决这个问题呢。哪种数据结构应该适合解决它。

4 个答案:

答案 0 :(得分:2)

我相信我在O(N + Q*log N)中有一个解决方案(更多关于time complexity)。诀窍是在第一个查询到达之前,先对你的数组进行 lot 准备。

  1. 对于每个号码,找出该号码左/右的第一个数字,它是严格更大的。
  2. 示例:对于数组:1, 8, 2, 3, 3, 5, 13左边的块都是8的位置,右边的块是5的位置。

    这可以在线性时间内确定。这是如何:在堆栈中保留一堆先前的最大值。如果出现新的最大值,则从堆栈中删除最大值,直到达到大于或等于当前值的元素。插图:

    Illustration

    在此示例中,堆栈中是:[15, 13, 11, 10, 7, 3](您当然会保留索引,而不是,我只会使用值更好的可读性)。

    现在我们阅读了88 >= 3,因此我们从堆栈中删除3并重复。 8 >= 7,删除78 < 10,所以我们停止删除。我们将10设置为8的左侧块,并将8添加到最大堆栈。

    此外,无论何时从堆栈中移除(在此示例中为37),请将已删除号码的右侧块设置为当前号码。但是有一个问题:右边的块会被设置为更大或更大的下一个数字,而不是更大。您只需检查并重新链接正确的块即可解决此问题。

    1. 计算一个子序列的最大值是多少次。
    2. 因为对于每个数字,您现在知道下一个左/右更大的数字在哪里,我相信您为此找到了合适的数学公式。

      然后,将结果存储在一个hashmap中,key将是一个数字的值,value将是该数字的最大值,是某个子序列的最大值。例如,记录[4->12]表示4个子序列中的12数是最大值。

      最后,将散列映射中的所有键值对提取到数组中,然后按键对该数组进行排序。最后,为该排序数组的值创建prefix sum

      1. 处理请求
      2. 对于请求“确切k”,只需在数组中进行二进制搜索,对more/less than k``进行二进制搜索,然后使用前缀数组。

答案 1 :(得分:1)

这个答案是我之前写的this other answer的改编。第一部分完全相同,但其他部分则针对这个问题。

这是使用简化版本的分段树实现的O(n log n + q log n)版本。

创建分段树:O(n)

在实践中,它的作用是采用数组,比方说:

A = [5,1,7,2,3,7,3,1]

构造一个支持数组的树,如下所示:

segment tree

在树中,第一个数字是值,第二个数字是数组中出现的索引。每个节点是其两个子节点中的最大节点。此树由数组(非常类似于堆树)支持,其中索引i的子项位于索引i*2+1i*2+2中。

然后,对于每个元素,很容易找到最近的更大元素(每个元素之前和之后)。

为了找到左边最近的更大元素,我们在树中搜索第一个父节点,其中左节点的值大于且索引小于参数。答案必须是这个父母的孩子,然后我们在树中寻找满足相同条件的最右边的节点。

类似地,为了找到最右边的更大元素,我们也这样做,但是寻找索引大于参数的正确节点。在下降时,我们会寻找满足条件的最左边的节点。

创建累积频率数组:O(n log n)

从这个结构中,我们可以计算频率数组,它表示每个元素在子数组列表中显示为最大值的次数。我们只需计算每个元素左侧和右侧有多少个较小的元素并将这些值相乘。对于示例数组([1, 2, 3]),这将是:

[(1, 1), (2, 2), (3, 3)]

这意味着1只出现一次最大值,2次出现两次等等。

但是我们需要回答范围查询,所以最好有这个数组的累积版本,如下所示:

[(1, 1), (2, 3), (3, 6)]

(3, 6)表示,有6个子阵列的最大值小于或等于3.

回答q次查询:O(q log n)

然后,要回答每个查询,您只需进行二进制搜索即可找到所需的值。例如。如果您需要找到3的确切数量,您可能需要执行以下操作:query(F, 3) - query(F, 2)。如果要查找小于3的那些,请执行:query(F, 2)。如果你想找到大于3的那些:query(F, float('inf')) - query(F, 3)

实施

我已经用Python实现了它,它似乎运行良好。

import sys, random, bisect
from collections import defaultdict
from math import log, ceil

def make_tree(A):
    n = 2**(int(ceil(log(len(A), 2))))
    T = [(None, None)]*(2*n-1)

    for i, x in enumerate(A):
        T[n-1+i] = (x, i)

    for i in reversed(xrange(n-1)):
        T[i] = max(T[i*2+1], T[i*2+2])

    return T

def print_tree(T):
    print 'digraph {'
    for i, x in enumerate(T):
        print '    ' + str(i) + '[label="' + str(x) + '"]'
        if i*2+2 < len(T):
            print '    ' + str(i)+ '->'+ str(i*2+1)
            print '    ' + str(i)+ '->'+ str(i*2+2)

    print '}'

def find_generic(T, i, fallback, check, first, second):
    j = len(T)/2+i
    original = T[j]
    j = (j-1)/2

    #go up in the tree searching for a value that satisfies check
    while j > 0 and not check(T[second(j)], original):
        j = (j-1)/2

    #go down in the tree searching for the left/rightmost node that satisfies check
    while j*2+1<len(T):
        if check(T[first(j)], original):
            j = first(j)
        elif check(T[second(j)], original):
            j = second(j)
        else:
            return fallback

    return j-len(T)/2


def find_left(T, i, fallback):
    return find_generic(T, i, fallback, 
        lambda a, b: a[0]>b[0] and a[1]<b[1],  #value greater, index before
        lambda j: j*2+2,                       #rightmost first
        lambda j: j*2+1                        #leftmost second
    ) 


def find_right(T, i, fallback):
    return find_generic(T, i, fallback,
        lambda a, b: a[0]>=b[0] and a[1]>b[1], #value greater or equal, index after
        lambda j: j*2+1,                       #leftmost first
        lambda j: j*2+2                        #rightmost second
    )       

def make_frequency_array(A):
    T = make_tree(A)

    D = defaultdict(lambda: 0)
    for i, x in enumerate(A):
        left = find_left(T, i, -1)
        right = find_right(T, i, len(A))
        D[x] += (i-left) * (right-i)

    F = sorted(D.items())
    for i in range(1, len(F)):
        F[i] = (F[i][0], F[i-1][1] + F[i][1])

    return F

def query(F, n):
    idx = bisect.bisect(F, (n,))
    if idx>=len(F): return F[-1][1]
    if F[idx][0]!=n: return 0
    return F[idx][1]

F = make_frequency_array([1,2,3])
print query(F, 3)-query(F, 2) #3 3
print query(F, float('inf'))-query(F, 4) #1 4
print query(F, float('inf'))-query(F, 1) #1 1
print query(F, 2) #2 3

答案 2 :(得分:0)

创建排序的值到索引的映射。例如,

[34,5,67,10,100] => {5:1, 10:3, 34:0, 67:2, 100:4}

通过value-to-index映射两次传递预先计算查询:

  1. 从上到下 - 维护一个增强的间隔树。每次添加索引时     拆分适当的间隔并从总数中减去相关的段:

    indexes    intervals    total sub-arrays with maximum greater than
    4          (0,3)        67 => 15 - (4*5/2)            = 5
    2,4        (0,1)(3,3)   34 => 5 + (4*5/2) - 2*3/2 - 1 = 11 
    0,2,4      (1,1)(3,3)   10 => 11 + 2*3/2 - 1          = 13
    3,0,2,4    (1,1)         5 => 13 + 1                  = 14
    
  2. 从下到上 - 维护一个增强的间隔树。每次添加索引时     调整适当的时间间隔并将相关的段添加到总数中:

    indexes    intervals    total sub-arrays with maximum less than
    1          (1,1)         10 => 1*2/2         = 1
    1,3        (1,1)(3,3)    34 => 1 + 1*2/2     = 2 
    0,1,3      (0,1)(3,3)    67 => 2 - 1 + 2*3/2 = 4
    0,1,3,2    (0,3)        100 => 4 - 4 + 4*5/2 = 10
    
  3. 第三个查询可以与第二个查询一起预先计算:

    indexes    intervals    total sub-arrays with maximum exactly
    1          (1,1)         5 =>         1
    1,3        (3,3)        10 =>         1 
    0,1,3      (0,1)        34 =>         2
    0,1,3,2    (0,3)        67 => 3 + 3 = 6
    
  4. augmented trees中的插入和删除时间复杂度为O(log n)。总预先计算时间复杂度为O(n log n)。之后的每个查询都应该是O(log n)时间复杂度。

答案 3 :(得分:0)

您的问题可以分为几个步骤:

  1. 对于初始数组的每个元素,计算当前元素最大的“子数组”的数量。这将涉及一些组合学。首先,您需要让每个元素知道更大的前一个和下一个元素的索引,而不是当前元素。然后计算子阵列的数量为(i - i prev )*(i next - i)。查找i prev 和i next 需要两次遍历初始数组:按正向和反向顺序。对于i prev ,您需要从左到右遍历数组。在遍历期间维护包含最大元素及其索引的BST。对于原始数组的每个元素,在BST中找到更大 minimal 元素,而不是当前元素。它作为值存储的索引将是i prev 。然后从BST中移除所有小于当前的元素。当您删除整个子树时,此操作应为O(logN)。此步骤是必需的,因为您要添加的当前元素将“覆盖”所有小于它的元素。然后将当前元素添加到BST,并将其索引作为值。在每个时间点,BST将存储先前元素的下降子序列,其中每个元素都比数组中的所有元素都大(对于之前的元素{1,2,44,5,2,6,26,6},BST将存储{44,26,6})。找到i next 的向后遍历类似。

  2. 在上一步之后,您将拥有K→P对,其中K是初始数组中某个元素的值,P是此元素为maxumum的子数组的数量。现在你需要用K对这一对进行分组。这意味着计算相等K元素的P值之和。当两个元素可以共享相同的子阵列时,请注意角落情况。

  3. 建议Ritesh:将所有分组的K→P放入一个数组中,按K排序并计算一次通过中每个元素的累计P值。在这种情况下,您的查询将是此排序数组中的二进制搜索。每个查询将在O(log(N))时间内执行。