优化在子阵列上执行的查询

时间:2014-11-01 09:48:43

标签: arrays algorithm optimization dynamic-programming mathematical-optimization

我最近在谷歌采访过。由于这个问题,我的过程在2轮之后没有向前推进。

  

假设您获得了一系列数字。您可以获得查询   到:

     
      
  1. 查找索引i和j之间的值之和。
  2.   
  3. 将索引i的值更新为新的给定值。
  4.   
  5. 查找索引i和j之间的最大值。
  6.   
  7. 检查索引i和j(包括两者)之间的子阵列是按升序还是降序排列。
  8.   

我给了他一个解决方案,但它是检查索引i和j之间的子阵列。他让我对它进行优化。我想使用哈希表,以便如果起始索引相同且结束索引大于上一个索引,我们存储最大值以及它是以升序还是降序并仅检查剩余的子阵列。但这并没有根据需要对其进行优化。 我很想知道如何优化解决方案以使其可以接受。

约束:

[1,10 ^ 5]

的所有内容

谢谢:)

2 个答案:

答案 0 :(得分:3)

在最糟糕的情况下,O(log N)每次查询可以回答所有这些查询(预处理O(N)时间)。您可以构建一个段树,并为每个节点维护总和,最大值和两个布尔标志(它们指示对应于此节点的范围是否按升序/降序排序)。对于更新查询,可以有效地重新计算所有这些值,因为只有O(log N)个节点可以更改(它们位于从根到与更改元素对应的叶子的路径上)。所有其他范围查询(sum,max,sorted or not)被分解为O(log N)个节点(由于段树的属性),并且很容易将O(1)中两个节点的值组合在一起(例如,对于sum,组合2个节点的结果只是这些节点的值的总和)。

这是一些伪代码。它显示了应该在节点中存储哪些数据以及如何组合2个节点的值:

class Node {
    bool is_ascending
    bool is_descending
    int sum
    int max
    int leftPos
    int rightPos
}

Node merge(Node left, Node right) {
    res = Node()
    res.leftPos = left.leftPos
    res.rightPos = right.rightPos
    res.sum = left.sum + right.sum
    res.max = max(left.max, right.max)
    res.is_ascending = left.is_ascending and right.is_ascending 
        and array[left.rightPos] < array[right.leftPos]
    res.is_descending = left.is_descending and right.is_descending 
        and array[left.rightPos] > array[right.leftPos]
    return res
 }

答案 1 :(得分:-1)

正如andy在评论中指出的:查询本质上是完全不同的,因此“最佳”解决方案可能取决于最常执行的查询类型。

然而,任务

  
      
  1. 查找索引i和j之间的值之和。
  2.   
通过执行阵列的扫描/前缀和计算可以有效地解决

。想象一下int

的数组
index:       0   1   2   3   4   5   6
array:    [  3,  8, 10, -5,  2, 12,  7 ]

然后你计算Prefix Sum

index:       0   1   2   3   4   5   6,  7 
prefix:   [  0,  3, 11, 21, 16, 18, 30, 37 ]

请注意,这可以并行计算特别有效。事实上,这是许多并行算法的一个重要组成部分,正如Guy E. Blelloch(thesis as PDF File)论文“数据并行计算的矢量模型”中所述。

此外,它可以通过两种方式计算:从array[0]开始,或以0开头。当然,这将影响必须如何访问结果前缀数组。在这里,我从0开始,使得结果数组比输入数组长一个元素。这也可能以不同的方式实现,但在这种情况下,它更容易遵守数组限制(尽管仍需要澄清在哪些情况下索引应被视为包含独占)。

但是,给定此前缀sum数组,可以通过简单地减去相应的值来计算常量时间内的索引ij之间的元素总和。前缀和数组:

sum(n=i..j)(array[n]) = (prefix[j+1] - prefix[i])

例如:

sum(n=2..5)(array[n])   = 10 + (-5) + 2 + 12 = 19
prefix[5+1] - prefix[2] = 30 - 11            = 19

对于任务2,

  
      
  1. 将索引i的值更新为新的给定值。
  2.   

这意味着必须更新前缀总和。这可以在线性时间内通过将旧值和新值的差值添加到修改后的元素后出现的所有前缀和来实现蛮力(但为此,也请参见本答案最后一节的注释)


任务3和4

  
      
  1. 查找索引i和j之间的最大值。
  2.   
  3. 检查索引i和j(包括两者)之间的子阵列是按升序还是降序排列。
  4.   

我可以想象在构建前缀和时可以简单地跟踪最大值,以及检查值是仅上升还是下降。但是,当值更新时,必须重新计算此信息。


在任何情况下,都有一些数据结构特别处理前缀和。我认为Fenwick tree 可能允许在O(logn)中实现上面提到的一些O(n)操作,但我还没有详细研究过这个。