簡體   English   中英

對於多個查詢,在數組中查找大於給定數字的第一個元素的最有效方法是什么?

[英]what is the most efficient way to find the first element greater than a given number in an array for multiple queries?

我有一組數字。 我的目標是在從起始索引向右移動時找到大於某個值 k 的第一個元素的索引。

例如,如果數組是 A = [4 3 3 4 6 7 1] 且 k = 3 且起始索引為 1(基於 0 的索引),則大於 k 的第一個數字的索引為 3。類似地,如果 k = 3 且起始索引 = 0,則第一個元素的索引為 0。

預處理很好,因為我需要針對不同的 k 值和起始索引處理多個此類查詢。

[更新]在任何“查找第一個索引”查詢之間也可能有一些數組更新查詢。 例如,index=1 和 value=2 的更新查詢會將 A 更改為 [4 5 3 4 6 7 1]

根據數據和查詢,采用天真的方法實際上可能更有效。 轉到起始索引,然后簡單地查找大於 k 的值。

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

你應該先試試這個。


如果您發現可以根據值(與基於起始索引)更快地縮小候選集的范圍,您也可以嘗試這種方法:

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

預處理步驟:有一個索引,生成按排序值排序的數組索引。 (如果您更新數組,您現在還必須更新索引。)

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

使用數組二分或二分搜索檢索值大於 k = 3 的數組索引列表。

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

過濾掉起始索引不大於起始索引 = 1 的候選。

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

通過迭代這個列表來選擇最小的數組索引。

result = 3

如果您以后想花一些內存來節省 CPU 周期,您可以添加記憶,甚至將所有可能查詢的所有結果預先計算到查找表中。 (並考慮緩存失效。)

如果您事先知道所有查詢,那么有一個時間復雜度為 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;
   }
} 

段樹方法:對於給定的值 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);

}

有關段樹的更多說明,請查看:段樹

算法步驟。

  1. 將數組值映射到索引,map.put(A[0],array[values])
  2. 使用一組唯一值/數組值對鍵進行排序(例如 Array A )
  3. 二進制搜索大於 K 的值的鍵
  4. 從地圖獲取索引作為 mapIndex[] = map.get(value > k)
  5. 找到索引使得 mapIndex[i] >= StartingIndex

制作相同大小的新數組 arr 並且元素 arr[i] 是原始數組中從 0 到 i 的數字的最大值,現在它的下限是你的值在這里輸入圖像描述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM