面試演算法:lg(k)時間查詢兩個排序陣列合並後第k小的元素
對於一個排好序的陣列A,如果我們要查詢第k小的元素,很簡單,只需要訪問A[k-1]即可,該操作的時間複雜度是O(1).假設給你兩個已經排好序的陣列A和B,他們的長度分別是m和n, 如果把A和B合併成一個排序陣列C, 陣列C含有m+n個元素,要求設計一個演算法,在lg(k)的時間內,找出陣列C中第k小的元素。
例如給定陣列:
A = {1, 3, 5, 7, 9}, B={2, 4, 6, 8, 10} , 合併後的陣列 C = {1,2,3,4,5,6,7,8,9,10}
如果k = 7, 那麼返回的元素是7,也就是A[3]。
一般的處理方法是,先把兩個陣列A和B合併成排好序的C,但是這個過程的時間複雜度是O(m+n), 當然我們可以優化一下,當合並時,只要合併的總元素達到k個就可以,然而這個時間複雜度是O(k),題目要求的時間複雜度是lg(k),是個比線性時間還要高的對數級複雜度。
這道題目是難度較大的演算法題,如果能在一個小時內給出演算法並寫出毛病不多的程式碼的話,那麼你的水平已經達到了技術經理的水準。
在演算法設計中,一種思維模式叫逆推法,要想獲得一個結果時,我們可以假設,一旦我們獲得了這個結果後,通過這個結果,我們可以推匯出這個結果會附帶著什麼樣的特殊性質,通過該性質,我們可以得到重大線索,進而推匯出獲取該結果的方法。
根據題目,我們要獲得合併後陣列第k小的元素,這意味著我們從合併陣列的前k個最小元素中,找到最大的那個元素,我們就得到了想要的答案。這前k個元素,要不全部來自陣列A, 要不全部來自陣列B, 要不一部分來自陣列A,一部分來自陣列B,如果k的值比某個陣列的所有元素還要大時,那麼前k個最小元素肯定包含陣列A的全部元素,於是要找到C[k-1], 我們只要找到max(A[m-1], B[k - m - 1])就可以了,例如:
A = {1, 2, 3, 4, 5}, B = {6, 7, 8, 9 ,10}, k = 7,
因此C[6] = max(A[4], B[2]) = B[2] = 7;
因此問題的難度在於第三種情況,也就是前k個最小元素一部分來自陣列A, 一部分來自陣列B。我們用逆推思維看看如何處理這種情況。假設前k個元素中,有l個來自陣列A, 有u個來自陣列B, l + u = k.
於是前k個元素的成分有:A[0], A[1]...A[l-1], B[0], B[1]...B[u-1]。從這個情況我們看看能推匯出什麼性質,我們先假設陣列中的元素不重複。
首先我們有 A[l] > B[u-1] , 要不然A[l] < B[u-1], 那麼我們把B[u-1]拿走,用A[l]替換,那麼所得的k個元素仍然滿足條件,這與我們假設B[u-1]屬於前k個元素的集合相矛盾。由於陣列A是排序的,於是有A[x] > B[u-1] 只要x > l - 1。
同時我們有A[l-1] < B[u], 要不然A[l-1] > B[u], 我們可以把A[l-1]從k個元素集合中拿走,用B[u]來替換,最後得到的k個元素集合仍然滿足條件,這與我們假設A[l-1]屬於k個元素的集合相矛盾,由於陣列A是排序的,因此有A[x] < B[u],只要x < l-1.
根據這兩個性質,我們只要通過查詢到 l-1, 那麼我們就可以找到 u - 1, 進而就能找到第k小的元素。我們可以通過在陣列A中,利用上面提到的兩個性質,通過折半查詢來找到 l - 1 的值。
於是演算法的基本步驟如下,如果陣列A的元素個數比k大,那麼我們就在陣列A的前k個元素中做折半查詢,如果陣列A的元素個數比k小,那麼就在整個陣列A中做折半查詢。
先在陣列A中折半,獲取中間元素假設是A[m/2], 如果A[m/2] > B[k - (m/2+1) - 1] (減1是因為陣列下標從0開始, m/2+1 表示A[m/2]前面包括它自己總共有m/2個元素)那麼l肯定落在0和m/2之間, 如果B[k-(m/2+1)-1] > A[m/2+1] , 那麼l肯定落在區間[m/2, m] 之間,確定區間後,在給定區間中繼續使用折半查詢法,一直找到正確的l為止。我們看個具體例項:
A = {1, 3, 5, 7, 9}, B = {2, 4, 6, 8 ,10}, k = 7
首先在A中折半查詢,找到的元素是A[2] = 5, 對應的B[7 - (2+1) - 1] = B[3] = 8, 此時B[3] > A[3], 所以對於的下標l坐落在區間[2, 4], 在區間[2,4]再次折半,於是得到下標3, A[3] = 7, B[7 - (3+1) -1] = B[2] = 6, 因為B[2] < A[4], 而且A[4] < B[3], 因此 l = 3, u = 2, 也就是說合並後前7小的數有4個來自陣列A, 也就是A[0],A[1],A[2],A[3], 有3個來自陣列B, 也就是B[0], B[1], B[2]。第k小的數只要比較A[3]和B[2],選出最大那個,根據本例,較大的是A[3], 也就是兩陣列合並後,第k小的數是A[3] = 7。
由於演算法只在一個陣列中折半查詢,並且查詢的範圍不超過k,因此整個演算法複雜度是lg(k),下面我們給出演算法的編碼實現:
public class KthElementSearch {
private int[] sortedArrayA;
private int[] sortedArrayB;
private int begin = 0;
private int end = 0;
private int requestElementCount = 0;
private int indexA = 0;
public KthElementSearch(int[] sortedA, int[] sortedB, int k) {
if (k < 0 || sortedA == null || sortedB == null) {
throw new IllegalArgumentException("illegal argument");
}
this.sortedArrayA = sortedA;
this.sortedArrayB = sortedB;
if (sortedA.length > k - 1) {
end = k - 1;
} else {
end = sortedA.length;
}
this.requestElementCount = k;
findGivenElement();
}
private void findGivenElement() {
int l = 0;
while (begin <= end) {
l = (begin + end) / 2;
/*因為陣列下標從0開始,所以計算當下標是m時,表示總共有l+1個,因此a[l]表示有l+1個元素,
* b[u]表示有u+1個元素,
*
*/
int u = requestElementCount - (l+ 1) - 1;
if (u+1 < sortedArrayB.length && sortedArrayA[l] > sortedArrayB[u+1]) {
end = l - 1;
}
else if (l + 1 < sortedArrayA.length && sortedArrayB[u] > sortedArrayA[l+1]) {
begin = l + 1;
} else {
break;
}
}
indexA = l;
}
public int getIndexFromFirstArray() {
return indexA;
}
public int getIndexFromSecondArray() {
return requestElementCount - (indexA + 1) - 1;
}
}
主入口函式的實現如下:
public static void main(String[] args) {
int[] A = new int[10];
int[] B = new int[10];
int[] C = new int[20];
int max = 20;
int min = 1;
Random random = new Random();
for (int i = 0; i < 10; i++) {
A[i] = random.nextInt(max) % (max - min + 1) + min;
}
Arrays.sort(A);
System.out.print("Array A is: ");
for(int i = 0; i < A.length; i++) {
System.out.print(A[i] + " ");
}
for (int i = 0; i < 10; i++) {
B[i] = random.nextInt(max) % (max - min + 1) + min;
}
Arrays.sort(B);
System.out.print("\nArray B is: ");
for(int i = 0; i < B.length; i++) {
System.out.print(B[i] + " ");
}
System.arraycopy(A, 0, C, 0, A.length);
System.arraycopy(B, 0, C, A.length, B.length);
Arrays.sort(C);
System.out.print("\nArra C is: ");
for (int i = 0; i < C.length; i++) {
System.out.print(C[i] + " ");
}
int k = 7;
System.out.println("\nThe " + k + "th element of combined array is:" + C[k-1]);
KthElementSearch kElement = new KthElementSearch(A, B, k);
int indexA = kElement.getIndexFromFirstArray();
System.out.println("Index of A is " + indexA + " value of element is: " + A[indexA]);
int indexB = kElement.getIndexFromSecondArray();
System.out.println("Index of B is " + indexB + " value of element is: " + B[indexB]);
}
在主函式中先構造兩個排好序的陣列A和B, 兩陣列中的元素值根據隨機數生成,然後把兩陣列合併成陣列C, 並且先輸出第k小的元素。接著構建KthElementSearch的例項,在該類的實現中,函式findGivenElement實現的就是我們前面說的折半查詢法,getIndexFromFirstArray()返回A陣列對應的元素下標,getIndexFromSecondArray()返回B陣列對應的元素下標,上面程式碼執行後,所得結果如下:
Array A is: 3 6 7 9 12 13 14 14 19 20
Array B is: 1 2 3 13 14 14 14 14 15 18
Arra C is: 1 2 3 3 6 7 9 12 13 13 14 14 14 14 14 14 15 18 19 20
The 7th element of combined array is:9
Index of A is 3 value of element is: 9
Index of B is 2 value of element is: 3
程式先建立了兩個排序陣列A,B,並分別列印出他們元素的內容,同時將兩陣列合併成陣列C, 並給出第7小的元素,它的值是9,接著輸出陣列A元素的對應下標是3, 也就是陣列A的前4個元素組成了合併後陣列C前7小元素的一部分,輸出第二個下標3對應的是陣列B, 也就是陣列B的前3個元素對應合併後陣列C前7小元素的一部分,通過資料對比可以發現,我們演算法得到的結論是正確的,合併後前7小的元素是:1 2 3 3 6 7 9,陣列A前4個元素是:3 6 7 9,陣列B前3個元素是:1 2 3。由此第7小的元素是A[3] = 9, 與程式列印的陣列C第7小元素完全吻合。
更詳細的程式碼除錯和講解請參看視訊:
如何進入google,演算法面試技能全面提升指南
更多技術資訊,包括作業系統,編譯器,面試演算法,機器學習,人工智慧,請關照我的公眾號:
相關文章
- 查詢陣列中第K大的元素陣列
- 陣列中的第K個最大元素陣列
- 【遞迴打卡2】求兩個有序陣列的第K小數遞迴陣列
- [譯] Swift 演算法學院 - 查詢陣列中第 K 大值Swift演算法陣列
- 尋找陣列中第K大的元素陣列
- 獲取一個陣列裡面第K大的元素陣列
- 215. 陣列中的第K個最大元素陣列
- TopK問題,陣列中第K大(小)個元素問題總結TopK陣列
- 第三章:查詢與排序(下)----------- 3.9 最快效率求出亂序陣列中第k小的數排序陣列
- 力扣-215. 陣列中的第K個最大元素力扣陣列
- LeetCode-215-陣列中的第K個最大元素LeetCode陣列
- 求陣列中k個數的所有組合陣列
- 34. 在排序陣列中查詢元素的第一個和最後一個位置(中)排序陣列
- js如何合併兩個陣列並且刪除重複的元素JS陣列
- 微策略面試題:在旋轉後的陣列中查詢元素(二分查詢)面試題陣列
- 陣列的主元素查詢陣列
- 第k大元素
- 無序陣列求第K大的數陣列
- 演算法-兩個排序陣列的中位數演算法排序陣列
- [LeetCode] Kth Largest Element in an Array (找出陣列的第k大的元素)LeetCode陣列
- 楊氏矩陣:查詢x是否在矩陣中,第K大數矩陣
- js查詢陣列元素位置JS陣列
- Java陣列排序和查詢Java陣列排序
- 合併K個排序連結串列排序
- 選擇問題——選取第K小元素
- 演算法合併排序陣列演算法排序陣列
- 兩個陣列分別取出一個來相加,找出和最小的k個陣列
- 【谷歌面試題】求陣列中兩個元素的最小距離谷歌面試題陣列
- Excel查詢兩列資料相同的元素Excel
- 選擇問題(求第k個最小元素)
- 【陣列】1539. 第 k 個缺失的正整數(簡單)陣列
- 215、陣列中的第K個最大元素 | 演算法(leetcode,附思維導圖 + 全部解法)300題陣列演算法LeetCode
- 【Java】陣列二分查詢元素Java陣列
- Javascript刷題 》 查詢陣列元素位置JavaScript陣列
- 合併兩個有序陣列陣列
- LC T668筆記 & 有關二分查詢、第K小數、BFPRT演算法筆記演算法
- 34、在排序陣列中查詢元素的第一個和最後一個位置 | 演算法(leetode,附思維導圖 + 全部解法)300題排序陣列演算法
- 獲取陣列第N個元素的方法陣列