查詢兩個有序陣列的中位數
問題描述
問題很簡單,就是在兩個有序的整數數組裡(陣列A長度為m, 陣列B長度為n),找到兩個數組合並後的中位數。
要求時間複雜度 O(log(m+n))
也就是說不能用先歸併兩個陣列再直接查詢的辦法。
中位數
中位數就是在一個有序陣列中,位於中間的數字,假如陣列元素個數為偶數,則取兩個中間數字的平均數。
例:
1,2,3,4,5 中位數為:3
1,2,3,4 的中位數為:2.5
演算法講解
這個問題其實看起來挺簡單的,網上的一些部落格裡說的,很多都是一個大致思想,細節有誤,程式碼也有誤。真是誤人子弟。
陣列等長
假如陣列等長的話,那麼很簡單就能解決,因為每次比較陣列的中間數字,把數字大的陣列扔掉右半邊,把數字小的陣列扔掉左半邊,每次迴圈,到最後就能知道中位數是多少了。
But!注意事項
你以為這麼簡單就OK了? 當然不是!在陣列等長的時候需要注意以下幾個問題。
- 陣列等長,說明陣列AB的元素和為偶數,最後求的的中位數一定是兩個中間數字的平均。
- 陣列在遞迴的分割過程中,出現兩個陣列都是偶數個數的時候,比較的其實不是中間的數字(是中間偏下的數字)。因此在遇到陣列如:
3,4,5,6與1,2,7,8
的時候,第一次比較是4
和2
發現4
比較大,因此把5,6
扔掉,另外一個數字扔掉1,2
,但是我們可以發現,這個兩個陣列的中位數是4,5
兩個數字的平均數4.5
,但是在剛才比較的過程中已經把5丟掉了。所以這個時候要注意。 - 陣列不是在遇見
A[median1] == B[median2]
median1 = 1, median2 = 1
, 那麼A[1] = 2, B[1] = 2
雖然兩個元素相等,但是這2並不是中位數,中位數是2.5
。但是假如在比較發現相等的時候,陣列比較的開始和結束是奇數個元素的話,是可以直接認為相等的。(例如: 1,2,3,4,5和1,2,3,5,7)這個兩個陣列的中位數就是3。
解決辦法
針對注意事項1:我們只用記得返回元素的時候一定是
return (double)(a+b)/2
就可以了。針對注意事項2:在判斷兩個中位數大小之後,請判斷當前(因為是遞迴,所以每次是有開始結束位置的)是不是兩個偶數個元素的中位數比較,如果是則在 A[median1] > B[median2]的時候,割掉(end1 - median1 - 1)個元素。同意陣列B割掉也是這麼多個元素。
針對注意事項3:
A: 1,2,3,4與B: 1,2,5,7
在遇到A[median1] == B[median2]
情況,雖然沒有直接停止,但是也是已經找到中位數了,同樣判斷當前比較的範圍是不是都是奇數個元素,如果是:返回這個數字!它就是中位數,如果不是,則返回這個數字2
與其後面數字A陣列2後面是3和B陣列2的後面5
相對較小數字3
的平均數(2 + min(3,5))/2
。
至此已經解決了等長度的陣列求中位數!
陣列不等長
其實陣列不等長與陣列等長的解決辦法基本一樣!
但是還是要注意每次遞迴切割的長度問題。
下面的圖可以簡單解釋一下分割的情況:
首先在我們切割的時候,先判斷陣列A和B的長短情況,哪個短,哪個是A,因為切割的時候如果以長陣列為準,有可能切掉一半後,短陣列早就越界訪問了。
注意:圖裡是
A[median1] < B[median2]
情況,假如是A[median1] > B[median2]
的情況,便如陣列等長情況一樣,需要判斷是不是雙方都是偶數個元素,如果是則切割的袁術個數不是end1 - median1
而是end1 - median1 - 1
個數字。
然後我們已經知道如何解決整個問題了。
遞迴結束條件
在陣列A,B元素個數和不同情況,結束條件不同。
奇數個數
陣列A和陣列B的元素個數和為奇數個:
A[median1] == B[median2]
一定結束。- 陣列A只有最後一個元素,假如
A[median1] < B[median2]
返回B[median2]
。假如A[median1] > B[median2] && A[median1] < B[median2+1]
返回A[median1]
。最後假如A[median1] > B[median2+1]
返回B[median2+1]
。
以上兩個是結束條件,那麼有一種情況是死迴圈條件。就是當迴圈到一定程度,即陣列A只剩下兩個元素的時候median1 == start1
,1,2
且比B[median2]
小的時候,已經沒有任何切割的了。就會死迴圈。(其實因為A[median1] > B[median2]
的時候會少切一個元素,所以不論比B[median2]
大或小都需要手動切割)。
這個時候手動切割掉A中不需要的元素就好了, A[median1]
小就切割掉A[median1]
反之切割掉A[end1]
。
然後繼續遞迴就好了,在上面已經說明了遇到A只有一個元素情況的結束條件。
注意!!!! 以上是建立在陣列A和陣列B總元素數量和為奇數情況。
偶數個數
偶數個數在結束條件上很麻煩!!!!
大家自己寫一個例子:
[1,2];[3,4];
---
[1,4];[2,3];
---
[1,3];[2,4,6,7];
---
[5,6];[1,2,4,7];
以上都需要一個個分析,分析A[start1]的位置和A[end1]的位置
。
想想什麼樣子的位置情況,應該是哪兩個數字的平均數就可以了。不難,但是亂!
程式碼(C++)
以下程式碼在leetcode OJ
上已經AC.
//
// main.cpp
// MedianOfTwoSortArray
//
// Created by Alps on 15/11/2.
// Copyright (c) 2015年 chen. All rights reserved.
//
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int length1 = (int)nums1.size();
int length2 = (int)nums2.size();
//如果有一個數組為空
if (length1 == 0) {
if (length2 %2 == 0) {
return (double)(nums2[length2/2]+nums2[length2/2 - 1])/2;
}else{
return (double)nums2[length2/2];
}
}
//如果有一個數組為空
if (length2 == 0) {
if (length1 %2 == 0) {
return (double)(nums1[length1/2]+nums1[length1/2 - 1])/2;
}else{
return (double)nums1[length1/2];
}
}
//如果把陣列長度短的放在前面
if (length1 <= length2) {
return findMedian(nums1, 0, length1-1, nums2, 0, length2-1);
}else{
return findMedian(nums2, 0, length2-1, nums1, 0, length1-1);
}
}
//返回相對小的數字
int minNumber(int a, int b){
return a < b ? a : b;
}
//查詢主函式
double findMedian(vector<int>& nums1, int start1, int end1, vector<int>& nums2, int start2, int end2){
// 如果兩個陣列都只有一個數字(為空的已經在之前判斷過了)
if (start1 == end1 && start2 == end2) {
return ((double)nums1[0]+(double)nums2[0])/2;
}
int offset = 0;//每次切割的偏移量
int median1 = (start1 + end1)/2;//求中間數字下標
int median2 = (start2 + end2)/2;//求中間數字下標
//陣列A只有一個數字,而陣列B不止一個數字的情況
if (start1 == end1 && start2 != end2) {
if ((end2 - start2) % 2 == 0) { //判斷陣列B如果是偶數個數字
if (nums1[start1] >= nums2[median2-1] && nums1[start1] <= nums2[median2+1]) {
return (double)(nums1[start1]+nums2[median2])/2;
}else if(nums1[start1] < nums2[median2-1]){
return (double)(nums2[median2-1] + nums2[median2])/2;
}else{
return (double)(nums2[median2] + nums2[median2+1])/2;
}
}else{//如果奇數個數字
if (nums1[start1] >= nums2[median2] && nums1[start1] <= nums2[median2+1]) {
return (double)nums1[start1];
}else if(nums1[start1] < nums2[median2]){
return (double)nums2[median2];
}else{
return (double)nums2[median2+1];
}
}
}
// 如果中間下標數字相等
if (nums1[median1] == nums2[median2]) {
if ((nums1.size() + nums2.size())%2 != 0 || end1-start1 % 2 == 0) {//總元素是奇數個或者兩個陣列範圍內都是奇數個元素
return nums1[median1];
}else{//否則
return (double)(nums1[median1]+minNumber(nums1[median1+1] , nums2[median2+1]))/2;
}
}else if (nums1[median1] < nums2[median2]){
if (median1 == start1) {//假如A範圍到兩個數字的情況了
if ((nums1.size() + nums2.size())%2 == 0) {//麻煩的判斷
if (nums1[start1+1] >= nums2[median2+1]) {
return (double)(nums2[median2]+nums2[median2+1])/2;
}else if(median2 != 0 && nums1[start1+1] <= nums2[median2-1]){
return (double)(nums2[median2]+nums2[median2-1])/2;
}else{
return (double)(nums2[median2]+nums1[start1+1])/2;
}
}else{
return findMedian(nums1, start1 + 1, end1, nums2, start2, end2-1);
}
}
offset = median1-start1;//正常的offset
return findMedian(nums1, start1+offset, end1, nums2, start2, end2-offset);
}else{
if (median1 == start1) {
if ((nums1.size() + nums2.size())%2 == 0) {
if (nums1[end1] <= nums2[median2+1]) {
return (double)(nums1[median1] + nums1[start1+1])/2;
}else if ((nums1[end1] > nums2[median2+1] && nums1[start1] <= nums2[median2+1]) || (median2+2 < nums2.size() && nums1[median1] <= nums2[median2+2]) || (median2+2 >= nums2.size() && nums1[start1] >= nums2[median2+1])){
return (double)(nums1[median1]+nums2[median2+1])/2;
}else {
return (double)(nums2[median2+1]+nums2[median2+2])/2;
}
}else{
return findMedian(nums1, start1, end1-1, nums2, start2+1, end2);
}
}
offset = end1-median1;
if ((end1-start1)%2 != 0 && (end2-start2)%2 != 0) {
offset -= 1; //需要少切割一個的offset
}
return findMedian(nums1, start1, end1-offset, nums2, start2+offset, end2);
}
return 0;
}
};
int main(int argc, const char * argv[]) {
Solution sl;
int num1[] = {1,5, 6, 7};
int num2[] = {2,3,4,8,9,10};
vector<int> nums1(num1, num1+4);
vector<int> nums2(num2, num2+6);
printf("%f\n",sl.findMedianSortedArrays(nums1, nums2));
return 0;
}
以上就是這個問題的解決程式碼了。我的程式碼量是稍微有點大的。所以可能不是很好看。後面我看看有沒有更簡單的程式碼解決辦法。