对于多个查询,在数组中查找大于给定数字的第一个元素的最有效方法是什么?

时间:2017-10-12 04:51:50

标签: arrays algorithm

我有一系列数字。我的目标是找到第一个元素的索引,它从起始索引到右边时大于某个值k。

例如,如果数组是A = [4 3 3 4 6 7 1]并且k = 3并且起始索引是1(基于0的索引),则大于k的第一个数的索引是3。 类似地,如果k = 3且起始索引= 0,则第一个元素的索引为0。

预处理很好,因为我需要为不同的k值和起始索引处理多个这样的查询。

[更新] 在任何“查找第一个索引”查询之间也可能存在一些数组更新查询。例如,index = 1且值为2的更新查询会将A更改为[4 5 3 4 6 7 1]

4 个答案:

答案 0 :(得分:1)

根据数据和查询,实施天真的方法实际上可能更有效。转到起始索引,然后只需查找大于k的值。

myStringBuilder.ToString()

你应该先尝试一下。

如果您发现可以根据值(相对于starting_index)更快地缩小候选集的范围,您也可以尝试这种方法:

BuildString()

预处理步骤:使索引生成按排序值排序的数组索引。 (如果更新数组,则现在还必须更新索引。)

array = [4, 3, 3, 4, 6, 7, 1]
# eliminate candidates based on starting_index
candidate_set = [3, 3, 4, 6, 7, 1]
# find index of first element greater than k in linear time
result = 2 + starting_index

使用数组二分或二分搜索检索值大于k = 3的数组索引列表。

array = [4, 3, 3, 4, 6, 7, 1]

过滤掉起始索引不大于起始索引= 1的候选人。

# first column value, second column array index
index = [(1, 6), (3, 1), (3, 2), (4, 0), (4, 3), (6, 4), (7, 5)]

通过迭代此列表来选择最小的数组索引。

candidate_set = [(4, 0), (4, 3), (6, 4), (7, 5)]

如果您有兴趣花一些时间来节省CPU周期,可以添加memoization,甚至可以将所有可能查询的所有结果预先计算到查找表中。 (并考虑缓存失效。)

答案 1 :(得分:1)

如果您事先知道所有查询,则会有一个时间复杂度为O(m log n)的算法,其中m是查询数,n是元素数。

从头到尾迭代遍历数组,并保持出队结构。

  • 在索引i处,我们尝试从出列的前面弹出所有小于索引i当前值的元素。然后将索引i附加到出列队列。我们可以很容易地看到出列的所有值都是按升序排列的。
  • 对于从索引i开始的所有查询,请在出列中使用二进制搜索来查找大于k的第一个元素

的伪代码:

Dequeue<Integer> q = new ArrayList<>();
for(int i = n - 1; i >= 0; i--){
    while(!q.isEmpty() && q.peek() <= data[i]){
         q.poll();
    }
    q.addFirst(i);
    for all query start at i {
         int st = 0;
         int ed = q.size();
         int re = -1;
         while(st <= ed){
             int mid = (st + ed)/2;
             if(data[q.get(mid)] > k){
                 re = q.get(mid); 
                 st = mid - 1;
             }else{
                 ed = mid + 1;
             }
         }
         print(re);
    }
}

由于数组可以实时更新,因此我们需要使用Segment Tree来跟踪数组每个段中的最大元素。

  • 对于每个查询,我们需要使用二进制搜索来搜索最大值大于k的最小段。

  • 时间复杂度O(m log log n),其中m是查询数,n是元素数。

的伪代码:

Build segment tree from input array

for each query {
   if update query{
       update tree
   }else{
       int startIndex = starting index for this query;
       int start = startIndex;
       int end = ending index;
       int re = -1;
       while(start <= end){
           int mid = (start + end)/2;
           //Getting the maximum value in segment [startIndex, mid]
           if(tree.maximumInSegment(startIndex, mid) > k){
                 re = mid;
                 end = mid - 1; 
           }else{
                 start = mid + 1;
           }
       }
       print re;
   }
} 

答案 2 :(得分:1)

段树方法: 对于给定的值 x 和范围 a[l…r],找到范围 a[l…r] 中的最小 i,使得 a[i] 大于 x。

这个任务可以通过使用 Segment Tree 对最大前缀查询的二分搜索来解决。但是,这将导致 O(log^2(n)) 解决方案。

通过树下降找到位置:通过每次向左或向右移动,取决于左孩子的最大值。从而在 O(logn) 时间内找到答案。

int get_first(int v, int lv, int rv, int l, int r, int x) {
if(lv > r || rv < l) return -1;
if(l <= lv && rv <= r) {
    if(t[v] <= x) return -1;
    while(lv != rv) {
        int mid = lv + (rv-lv)/2;
        if(t[2*v] > x) {
            v = 2*v;
            rv = mid;
        }else {
            v = 2*v+1;
            lv = mid+1;
        }
    }
    return lv;
}

int mid = lv + (rv-lv)/2;
int rs = get_first(2*v, lv, mid, l, r, x);
if(rs != -1) return rs;
return get_first(2*v+1, mid+1, rv, l ,r, x);

}

有关段树的更多说明,请查看:Segment Tree

答案 3 :(得分:0)

算法步骤。

  1. 将数组值映射到索引map.put(A [0],array [values])
  2. 使用一组唯一值/数组值(在示例数组A中)对键进行排序
  3. 二进制搜索值大于K的键
  4. 从map中获取索引mapIndex [] = map.get(value&gt; k)
  5. 找到索引,使mapIndex [i]&gt; = StartingIndex