[英]What is an efficient way to find the first occurrence of an element after a given position in an array?
[英]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);
}
有關段樹的更多說明,請查看:段樹
算法步驟。
制作相同大小的新數組 arr 並且元素 arr[i] 是原始數組中從 0 到 i 的數字的最大值,現在它的下限是你的值在這里輸入圖像描述
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.