[英]Find the number of elements greater than x in a given range
給定一個包含 n 個元素的數組,如何以 O(log n) 復雜度在給定范圍索引 i 到索引 j 中找到大於或等於給定值 (x) 的元素數量?
查詢的形式為 (i, j, x),這意味着從數組中的第 i 到第 j 個元素中查找大於 x 的元素數
數組未排序。 i, j & x 對於不同的查詢是不同的。 數組的元素是靜態的。 編輯:對於不同的查詢,i、j、x 都可以不同!
如果我們事先知道所有查詢,我們可以通過使用Fenwick 樹來解決這個問題。
首先,我們需要根據它們的值對數組和查詢中的所有元素進行排序。
因此,假設我們有數組 [5, 4, 2, 1, 3] 和查詢 (0, 1, 6) 和 (2, 5, 2),我們將在排序后得到以下結果: [1, 2, 2 , 3 , 4 , 5, 6]
現在,我們需要按降序處理每個元素:
如果我們遇到一個來自數組的元素,我們將更新它在 Fenwick 樹中的索引,這需要 O(log n)
如果遇到查詢,我們需要檢查在查詢的這個范圍內,樹中添加了多少元素,需要 O(log n)。
對於上面的例子,過程將是:
1st element is a query for value 6, as Fenwick tree is empty -> result is 0
2nd is element 5 -> add index 0 into Fenwick tree
3rd element is 4 -> add index 1 into tree.
4th element is 3 -> add index 4 into tree.
5th element is 2 -> add index 2 into tree.
6th element is query for range (2, 5), we query the tree and get answer 2.
7th element is 1 -> add index 3 into tree.
Finish.
因此,總的來說,我們的解決方案的時間復雜度為 O((m + n) log(m + n)),其中 m 和 n 分別是來自輸入數組的查詢數和元素數。
只有當您對數組進行排序時,這才是可能的。 在這種情況下,二進制搜索通過您的條件的最小值並通過將您的索引范圍除以其找到的位置細分為兩個間隔來計算計數。 然后只需計算通過您的條件的間隔的長度。
如果數組未排序並且您需要保留其順序,則可以使用索引 sort 。 放在一起時:
定義
讓<i0,i1>
是您使用的索引范圍, x
是您的值。
索引排序數組部分<i0,i1>
所以創建大小為m=i1-i0+1
數組並對其進行索引排序。 這個任務是O(m.log(m))
其中m<=n
。
二分查找x
在索引數組中的位置
這個任務是O(log(m))
並且你想要索引j = <0,m)
其中array[index[j]]<=x
是最小值<=x
計算計數
簡單地計算j
到m
之后有多少個索引
count = mj;
如您所見,如果數組已排序,您的復雜度為O(log(m))
但如果不是,則您需要對O(m.log(m))
進行排序,這比簡單的方法O(m)
更糟糕,這應該是僅在數組經常更改且無法直接排序時使用。
[Edit1] 我所說的索引排序是什么意思
通過索引排序,我的意思是:讓數組a
a[] = { 4,6,2,9,6,3,5,1 }
索引排序意味着您按排序順序創建索引的新數組ix
因此例如升序索引排序意味着:
a[ix[i]]<=a[ix[i+1]]
在我們的例子中,索引冒泡排序是這樣的:
// init indexes
a[ix[i]]= { 4,6,2,9,6,3,5,1 }
ix[] = { 0,1,2,3,4,5,6,7 }
// bubble sort 1st iteration
a[ix[i]]= { 4,2,6,6,3,5,1,9 }
ix[] = { 0,2,1,4,5,6,7,3 }
// bubble sort 2nd iteration
a[ix[i]]= { 2,4,6,3,5,1,6,9 }
ix[] = { 2,0,1,5,6,7,4,3 }
// bubble sort 3th iteration
a[ix[i]]= { 2,4,3,5,1,6,6,9 }
ix[] = { 2,0,5,6,7,1,4,3 }
// bubble sort 4th iteration
a[ix[i]]= { 2,3,4,1,5,6,6,9 }
ix[] = { 2,5,0,7,6,1,4,3 }
// bubble sort 5th iteration
a[ix[i]]= { 2,3,1,4,5,6,6,9 }
ix[] = { 2,5,7,0,6,1,4,3 }
// bubble sort 6th iteration
a[ix[i]]= { 2,1,3,4,5,6,6,9 }
ix[] = { 2,7,5,0,6,1,4,3 }
// bubble sort 7th iteration
a[ix[i]]= { 1,2,3,4,5,6,6,9 }
ix[] = { 7,2,5,0,6,1,4,3 }
所以升序索引排序的結果是這樣的:
// ix: 0 1 2 3 4 5 6 7
a[] = { 4,6,2,9,6,3,5,1 }
ix[] = { 7,2,5,0,6,1,4,3 }
原始數組保持不變,只是索引數組發生了變化。 項目a[ix[i]]
其中i=0,1,2,3...
按升序排序。
所以現在如果x=4
在這個區間你需要找到(bin search) i
最小但仍然a[ix[i]]>=x
所以:
// ix: 0 1 2 3 4 5 6 7
a[] = { 4,6,2,9,6,3,5,1 }
ix[] = { 7,2,5,0,6,1,4,3 }
a[ix[i]]= { 1,2,3,4,5,6,6,9 }
// *
i = 3; m=8; count = m-i = 8-3 = 5;
所以答案是5
項目>=4
[Edit2] 只是為了確保您知道二進制搜索對此意味着什么
i=0; // init value marked by `*`
j=4; // max power of 2 < m , i+j is marked by `^`
// ix: 0 1 2 3 4 5 6 7 i j i+j a[ix[i+j]]
a[ix[i]]= { 1,2,3,4,5,6,6,9 } 0 4 4 5>=4 j>>=1;
* ^
a[ix[i]]= { 1,2,3,4,5,6,6,9 } 0 2 2 3< 4 -> i+=j; j>>=1;
* ^
a[ix[i]]= { 1,2,3,4,5,6,6,9 } 2 1 3 4>=4 j>>=1;
* ^
a[ix[i]]= { 1,2,3,4,5,6,6,9 } 2 0 -> stop
*
a[ix[i]] < x -> a[ix[i+1]] >= x -> i = 2+1 = 3 in O(log(m))
所以你需要索引i
和二進制位掩碼j
(2 的冪)。 首先設置i
為零, j
的最大冪為 2 仍然小於n
(或在這種情況下m
)。 例如這樣的事情:
i=0; for (j=1;j<=m;j<<=1;); j>>=1;
現在在每次迭代中測試a[ix[i+j]]
滿足搜索條件。 如果是,則更新i+=j
否則保持原樣。 之后轉到下一位,因此j>>=1
並且如果j==0
停止否則再次進行迭代。 最后你發現值是a[ix[i]]
並且索引是i
在log2(m)
迭代中,這也是表示m-1
所需的位數。
在上面的示例中,我使用條件a[ix[i]]<4
因此找到的值是數組中仍然<4
最大數字。 因為我們還需要包含4
所以我只在最后增加一次索引(我可以使用<=4
代替,但懶得重新重寫整個事情)。
這些項目的數量就是數組(或間隔)中的元素數減去i
。
先前的答案描述了使用 Fenwick 樹的離線解決方案,但可以在線解決此問題(甚至在對陣列進行更新時),但復雜性稍差。 我將使用段樹和 AVL 樹來描述這樣的解決方案(任何自平衡 BST 都可以解決問題)。
首先讓我們看看如何使用段樹解決這個問題。 我們將通過按它覆蓋的范圍將數組的實際元素保留在每個節點中來做到這一點。 所以對於數組A = [9, 4, 5, 6, 1, 3, 2, 8]
我們將有:
[9 4 5 6 1 3 2 8] Node 1
[9 4 5 6] [1 3 2 8] Node 2-3
[9 4] [5 6] [1 3] [2 8] Node 4-7
[9] [4] [5] [6] [1] [3] [2] [8] Node 8-15
由於我們的段樹的高度是log(n)
並且在每個級別我們保留 n 個元素,因此使用的內存總量是n log(n)
。
下一步是對這些數組進行排序,如下所示:
[1 2 3 4 5 6 8 9] Node 1
[4 5 6 9] [1 2 3 8] Node 2-3
[4 9] [5 6] [1 3] [2 8] Node 4-7
[9] [4] [5] [6] [1] [3] [2] [8] Node 8-15
注意:您首先需要構建樹,然后對其進行排序以保持原始數組中元素的順序。
現在我們可以開始我們的范圍查詢,它的工作方式與常規線段樹基本相同,除了當我們發現一個完全重疊的區間時,我們會額外檢查大於 X 的元素數量。 這可以通過日志中的二分搜索來完成(n) 通過找到大於 X 的第一個元素的索引並從該間隔中的元素數中減去它來計算時間。
假設我們的查詢是(0, 5, 4)
,所以我們在區間[0, 5]
上進行段搜索並最終得到數組: [4, 5, 6, 9], [1, 3]
。 然后我們對這些數組進行二分搜索以查看大於 4 的元素數量,並得到 3(來自第一個數組)和 0(來自第二個數組),總共 3 -我們的查詢答案。
段樹中的間隔搜索最多可以有log(n)
路徑,這意味着log(n)
數組,並且由於我們對每個數組進行二分搜索,因此每個查詢都會增加log^2(n)
復雜性。
現在如果我們想更新數組,因為我們使用的是段樹,所以不可能有效地添加/刪除元素,但我們可以替換它們。 使用 AVL 樹(或其他允許在 log(n) 時間內替換和查找的二叉樹)作為節點並存儲數組,我們可以在相同的時間復雜度(用log(n)
時間替換log(n)
管理此操作。
這是 2D 中正交范圍計數查詢的特殊變體。 每個元素el[i]
被轉換為平面上的點(i, el[i])
並且查詢(i,j,x)
可以轉換為計算矩形[i,j] x [x, +infty]
。
您可以將 2D 范圍樹(例如: http : //www.cs.uu.nl/docs/vakken/ga/slides5b.pdf )用於此類查詢。
簡單的想法是有一個樹來存儲按 X 軸排序的葉子中的點(每個葉子包含一個點)。 樹的每個內部節點都包含額外的樹,用於存儲子樹中的所有點(按 Y 軸排序)。 已用空間為O(n logn)
簡單版本可以在O(log^2 n)
時間內進行計數,但使用分數級聯可以將其減少到O(log n)
。
Chazelle 在 1988 年 ( https://www.cs.princeton.edu/~chazelle/pubs/FunctionalDataStructures.pdf ) 有更好的解決方案,以O(n)
預處理和O(log n)
查詢時間。
您可以找到一些查詢時間更短的解決方案,但它們要復雜得多。
我會嘗試給你一個簡單的方法。
你一定學過歸並排序。 在合並排序中,我們繼續將數組划分為子數組,然后重新構建它,但是在這種方法中我們不存儲排序的子數組,而是將它們存儲為二叉樹的節點。
這會占用 nlogn 空間和 nlogn 時間來構建; 現在對於每個查詢,您只需要找到子數組,這將平均在 logn 中完成,在最壞的情況下以 logn^2 完成。
這些樹也被稱為芬威克樹。 如果你想要一個簡單的代碼,我可以為你提供。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.