Description
There are two sorted arrays nums1 and nums2 of size m and n respectively.
Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).
Example 1
nums1 = [1, 3]
nums2 = [2]
The median is 2.0
Example 2
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5
初始分析: O(m+n)
首先,分析題目要求,該函數:輸入參數為兩個有序數組,輸出要求為這兩個數組內所有數字集的中位數。
*額外要求:算法的時間復雜度小于等于 O(log (m+n)).
先撇開額外要求不談,碰到兩個有序數組的合并,我首先想到的是歸并排序(Merge Sort)的思想。歸并排序本身就是不斷遞歸地分割原始數組再進行子數組排序,最后合并這些有序子數組。而此處我只需要做有序數組的合并。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int[] sorted = mergeSort(nums1, nums2);
if((sorted.length&1)==1){
return sorted[sorted.length/2];
}else{
return (sorted[sorted.length/2 - 1] + sorted[sorted.length/2])/2.0;
}
}
public int[] mergeSort(int[] a, int[] b){
int[] output = new int[a.length + b.length];
int lt = 0;
int rt = 0;
int index = 0;
while(lt<a.length && rt<b.length){
if(a[lt]<b[rt]){
output[index++] = a[lt++];
}else{
output[index++] = b[rt++];
}
}
if(lt!=a.length){
System.arraycopy(a, lt, output, lt+rt, a.length-lt);
}else{
System.arraycopy(b, rt, output, lt+rt, b.length-rt);
}
return output;
}
}
基于這個想法的代碼大概長這樣(有可以改進的地方歡迎提出)。
時間復雜度為O(m+n),能被AC,打敗了40%的java算法。
深度分析: O(log(min(m,n))),思路參考LeetCode網友MissMary
首先理解中位數的代數意義:
中位數, 統計學中的專有名詞,代表一個樣本、種群或概率分布中的一個數值,其可 將數值集合劃分為長度相等的上下兩部分。
關鍵在于后半句: 將數值集合劃分為長度相等的上下兩部分
除此之外,對于劃分后的兩組數字,其中一組內的數字總是大于另一組內的數字。
舉個例子,先把第一個數組(稱為數組A)在隨機位置 i 處分割開:
左半邊 | 右半邊 |
---|---|
A[0], A[1],....,A[i-1] | A[i], A[i+1],...,A[m-1] |
同樣地,在隨機位置 j 處 將數組B分割開:
左半邊 | 右半邊 |
---|---|
B[0], B[1],....,B[j-1] | B[j], B[j+1],...,B[n-1] |
將A、B數組的左半邊放到一起,右半邊放到一起, 可得:
左半邊 | 右半邊 |
---|---|
A[0], A[1],....,A[i-1] | A[i], A[i+1],...,A[m-1] |
B[0], B[1],....,B[j-1] | B[j], B[j+1],...,B[n-1] |
如果這樣的分組能滿足下面兩個條件:
1. len(左半邊)==len(右半邊)
2. max(左半邊)<=min(右半邊)
就說明我們就成功的將{A,B}中的所有元素分割成了等長的兩部分,且左半邊的數字總小于右半邊的數字。
這時,中位數可以很輕易地通過 Median = (max(左半邊)+min(右半邊))/2 計算出來。
為了滿足這兩個條件,我們只需要保證:
1). 左右兩部分等長:
i + j == m - i + n - j (或者: m - i + n - j + 1)
假設 n >= m, 只需要讓: i = 0 ~ m, j = (m + n + 1)/2 - i
2). B[j-1] <= A[i] and A[i-1] <= B[j]
為什么需要假設n>=m? 因為如果m>n, 則B數組的下標索引 j 有可能變成負數。
對于 i 和 j 等于0的邊緣情況,留到后面討論
所以,簡而言之,程序需要做的僅僅是:
在[0, m]中尋找合適的分割點 i , 使得:
B[j-1] <= A[i] and A[i-1] <= B[j], ( 其中 j = (m + n + 1)/2 - i )
這個過程可以通過二分查找法提高效率:
<1> 設 imin = 0, imax = m, 然后在 [imin, imax] 中進行查找
<2> 設 i = (imin + imax)/2, j = (m + n + 1)/2 - i
<3> 接下來有三種情況:
<a> B[j-1] <= A[i] and A[i-1] <= B[j]
//找到了合適的分割點 i, 停止搜索
<b> B[j-1] > A[i]
//A[i] 的值偏小. 需要調整 i 使得 `B[j-1] <= A[i]`.
//此處應該增大 i 值,因為當 i 值增大時, j 值會減小.
//因此B[j-1]會隨A[i]的增大而減小, 更容易滿足 `B[j-1] <= A[i]`
//所以應將搜索范圍調整為 [i+1, imax]. 即: 使 imin = i+1, 然后回到步驟 <2>.
<c> A[i-1] > B[j]
//與前一種情況的處理方式相反,即: 使 imax = i-1,然后回到步驟<2>.
當找到合適的分割點 i 后,易知中位數為:
當 m + n 為奇數:max(A[i-1], B[j-1])
當 m + n 為偶數:(max(A[i-1], B[j-1]) + min(A[i], B[j]))/2
再來考慮邊緣情況,即: 當 i=0,i=m,j=0,j=n 時 A[i-1],B[j-1],A[i],B[j] 有可能不存在的情況:
其實這些情況非常容易分析,因為我們所要滿足的僅僅是 max(左半邊) <= min(右半邊) 這個條件,假設A[i-1],B[j-1],A[i],B[j]都存在,則該條件可表示為 B[j-1] <= A[i] and A[i-1] <= B[j]。如果有些值不存在的話則完全不用去判斷,比如我們假設 i=0, 則 A[i-1]不存在,我們便不用判斷 A[i-1] <= B[j]這個條件,因為此時數組A不存在左半邊。所以,考慮邊緣情況后,搜索思路大致為:
在 [0, m] 中尋找合適的分割點 i, 使得:
(j == 0 or i == m or B[j-1] <= A[i]) and
(i == 0 or j == n or A[i-1] <= B[j]),
其中 j = (m + n + 1)/2 - i
在搜索的過程中,會出現下面三種情況:
<a> (j == 0 or i == m or B[j-1] <= A[i]) and
(i == 0 or j = n or A[i-1] <= B[j])
//找到合適的 i 值,停止搜索
<b> j > 0 and i < m and B[j - 1] > A[i]
// i 值偏小,需要增加 i 值
<c> i > 0 and j < n and A[i - 1] > B[j]
// i 值偏大,需要減小 i 值
其中<b>和<c>的判斷條件可以進一步簡化為
<b> i < m and B[j - 1] > A[i]
<c> i > 0 and A[i - 1] > B[j]
因為當 i < m時,j一定大于0,且 i>0 時,j一定小于n,推導過程為:
m <= n, i < m ==> j = (m+n+1)/2 - i > (m+n+1)/2 - m >= (2*m+1)/2 - m >= 0
m <= n, i > 0 ==> j = (m+n+1)/2 - i < (m+n+1)/2 <= (2*n+1)/2 <= n
基于上述的思路的Java代碼如下:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2)
{
int m = nums1.length;
int n = nums2.length;
if(m>n)
{
int[] tmp = nums1;
nums1 = nums2;
nums2 = tmp;
m=m^n;
n=m^n;
m=m^n;
} //交換m與n的值,以及nums1和nums2的引用
int imin = 0;
int imax = m;
int half = (m+n+1)>>1;
while(imin<=imax)
{
int i = (imin + imax)>>1;
int j = half - i;
if(i<m && nums2[j-1]>nums1[i])
imin = ++i; //i值偏小
else if(i>0 && nums1[i-1]>nums2[j])
imax = --i; //i值偏大
else //完美情況
{
int max_left = 0;
if(i==0)
max_left = nums2[j-1];
else if(j==0)
max_left = nums1[i-1];
else
max_left = Math.max(nums1[i-1], nums2[j-1]);
if(((m+n)&1)==1)
return max_left; //m+n為奇數的情況中位數為max_left
int min_right = 0;
if(i==m)
min_right = nums2[j];
else if(j==n)
min_right = nums1[i];
else
min_right = Math.min(nums1[i], nums2[j]);
return (max_left+min_right)/2.0; //m+n為偶數的情況中位數為(max_left+min_right)/2
}
}
return -1;
}
}
上述算法的時間復雜度為O(log(min(m,n))), 代碼部分有可以改進的地方歡迎提出。