資料結構演算法題/兩個有序陣列的中位數
有三種方法,時間複雜度分別是O(m+n) ,O(k),O(log(m+n))
注意點:
判斷合併後的陣列的元素個數是奇數還是偶數
如果是奇數取中間值;如果是偶數取中間2個數的平均值。
兩種求中位數的方法:
(1)方法1,判斷奇數個還是偶數個
if (lengthall % 2 == 0){ result = ( all[lengthall/2-1] + all[lengthall/2] )*1.0/2; }else{ result = all[lengthall/2]; } (2)方法2,不需要判斷奇數個還是偶數個 int l = (lengthall+1)/2-1; int r = (lengthall+2)/2-1; //上面減1的原因是座標從0開始的 result = (all[l] + all[r])*1.0/2; //這種求中位數的方法不需要判斷是奇數還是偶數個
思考:
中位數相當於合併之後的top(n/2),如果是其他top數也是可以的。所以如果是一個無序陣列的top k就只用小頂堆。如果是兩個有序數組合並之後的top k就可以用如下的方法。
(1)O(m+n)合併兩有序陣列成一個新有序陣列,再按中間位置取值。
兩個有序數組合併為一個有序陣列的時間複雜度O(m+n)。請參考https://blog.csdn.net/fkyyly/article/details/83145276
/**1 * O(m+n) * 合併兩有序陣列成一個新有序陣列,再按中間位置取值 * @param nums1 * @param nums2 * @return */ public static double findMedianSortedArrays1(int[] nums1, int[] nums2) { int length1 = nums1.length; int length2 = nums2.length; int lengthall = length1 + length2; int[] all = new int[length1+length2]; int i=0,j=0,k=0; while (i<length1 && j <length2){ if(nums1[i]<nums2[j]){ all[k]=nums1[i]; i++; k++; }else{ all[k] = nums2[j]; j++; k++; } } while (i<length1){ all[k]=nums1[i]; i++; k++; } while (j<length2){ all[k] = nums2[j]; j++; k++; } double result; if (lengthall % 2 == 0){ result = ( all[lengthall/2-1] + all[lengthall/2] )*1.0/2; }else{ result = all[lengthall/2]; } return result; }
(2)O(k)兩個指標分別從兩陣列開頭指,比較兩指標處的數,誰小誰往後移,直到兩指標共移動k次,k為中間位置
/**2 * O(k) * 兩個指標分別從兩陣列開頭指,比較兩指標處的數,誰小誰往後移, * 直到兩指標共移動k次,k為中間位置 * 注意總數有奇數個還是偶數個計算中位數的方法 * @param nums1 * @param nums2 * @return */ public static double findMedianSortedArrays2(int[] nums1, int[] nums2) { int length1 = nums1.length; int length2 = nums2.length; int lengthall = length1 + length2; int inx1 = 0; int inx2 = 0; int x = -1; int y = -1; while (inx1 + inx2 < lengthall){ if (inx1 < length1) { while (inx2 == length2 || nums1[inx1] <= nums2[inx2] ) { //nums1到頭了或者nums1此時的元素小於nums2此時的元素,這兩種情況下從nums1取值(也就是nums1和nums2小的移動) inx1++; //下面是總長度是奇數還是偶數對於中位數的計算 if (inx1 + inx2 == (lengthall + 1) / 2) { x = nums1[inx1 - 1]; } if (inx1 + inx2 == (lengthall + 2) / 2) { y = nums1[inx1 - 1]; return (x + y) * 1.0 / 2; } if (inx1 == length1){ break; } } } if (inx2 < length2){ while ( inx1 == length1 || nums2[inx2] <= nums1[inx1] ) { inx2++; if (inx1 + inx2 == (lengthall + 1)/2){ x = nums2[inx2-1]; } if (inx1 + inx2 == (lengthall + 2)/2){ y = nums2[inx2-1]; return (x+y)*1.0/2; } if (inx2 == length2){ break; } } } } return -1; }
(3)O(log(m+n))兩個陣列分別採用二分法查詢
思想:上面的是陣列num1[],下面是陣列num2[]。
使用遞迴的思想分別對num1和num2求中位數,因為是有序的,所以直接取n/2處的值即可。
(1)如果num1的中位數=num2的中位數,那麼就是最終的結果。
(2)如果num1的中位數<num2的中位數,那麼下次就在[B,C]和[D,E]兩個新陣列直接找中位數。
(3)如果num1的中位數>num2的中位數,那麼下次就在[A,B]和[E,F]兩個新陣列直接找中位數。
(4)重複這個步驟,直到新陣列的長度為2
如果陣列a的中位數小於陣列b的中位數,那麼整體的中位數只可能出現在a的右區間加上b的左區間之中;
如果陣列a的中位數大於等於陣列b的中位數,那麼整體的中位數只可能出現在a的左區間加上b的右區間之中。
關鍵就是利用分治的思想逐漸縮小a的區間和b的區間來找到中位數。
首先假設陣列A和B的元素個數都大於k/2,我們比較A[k/2-1]和B[k/2-1]兩個元素,這兩個元素分別表示A的第k/2小的元素和B的第k/2小的元素。這兩個元素比較共有三種情況:>、<和=。如果A[k/2-1]<B[k/2-1],這表示A[0]到A[k/2-1]的元素都在A和B合併之後的前k小的元素中。換句話說,A[k/2-1]不可能大於兩數組合並之後的第k小值,所以我們可以將其拋棄。
當A[k/2-1]>B[k/2-1]時存在類似的結論。
當A[k/2-1]=B[k/2-1]時,我們已經找到了第k小的數,也即這個相等的元素,我們將其記為m。由於在A和B中分別有k/2-1個元素小於m,所以m即是第k小的數。(這裡可能有人會有疑問,如果k為奇數,則m不是中位數。這裡是進行了理想化考慮,在實際程式碼中略有不同,是先求k/2,然後利用k-k/2獲得另一個數。)
通過上面的分析,我們即可以採用遞迴的方式實現尋找第k小的數。此外我們還需要考慮幾個邊界條件:
如果A或者B為空,則直接返回B[k-1]或者A[k-1];
如果k為1,我們只需要返回A[0]和B[0]中的較小值;
如果A[k/2-1]=B[k/2-1],返回其中一個;
https://blog.csdn.net/hjhjhx26364/article/details/80251675
https://blog.csdn.net/darminz/article/details/77500474