寻找两个有序数组的中位数
Median of Two Sorted Arrays
题目描述
解法: 递归法
为了解决这个问题,我们需要理解 “中位数的作用是什么”。在统计中,中位数被用来:
将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。
如果理解了中位数的划分作用,我们就很接近答案了。
首当其冲的来讲,让我们在任一位置 i i i 将 A \text{A} A 划分成两个部分:
left_A | right_A
A[0], A[1], ..., A[i-1] | A[i], A[i+1], ..., A[m-1]
由于 A \text{A} A 中有 m m m 个元素, 所以我们有 m + 1 m+1 m+1 种划分的方法( i = 0 ∼ m i = 0 \sim m i=0∼m)。
因此我们可以得知:
l
e
n
(
l
e
f
t
A
)
=
i
,
l
e
n
(
r
i
g
h
t
A
)
=
m
−
i
.
len(left_A)=i,len(right_A)=m−i.
len(leftA)=i,len(rightA)=m−i.
当
i
=
0
i = 0
i=0的时候,
l
e
f
t
A
left_A
leftA为空集,而当
i
=
m
i = m
i=m的时候,
r
i
g
h
t
A
right_A
rightA为空集。
采用同样的方式,我们在任一位置 j j j将 B \text{B} B 划分成两个部分:
left_B | right_B
B[0], B[1], ..., B[j-1] | B[j], B[j+1], ..., B[n-1]
将 left_A \text{left\_A} left_A 和 left_B \text{left\_B} left_B 放入一个集合,并将 right_A \text{right\_A} right_A 和 right_B \text{right\_B} right_B 放入另一个集合。 再把这两个新的集合分别命名为 left_part \text{left\_part} left_part 和 right_part \text{right\_part} right_part:
left_part | right_part
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]
那么当我们可以确认一个情况的时候:
- l e n ( left_part ) = l e n ( right_part ) len(\text{left\_part})=len(\text{right\_part}) len(left_part)=len(right_part)
- m a x ( left_part ) ≤ m i n ( right_part ) max(\text{left\_part}) ≤ min(\text{right\_part}) max(left_part)≤min(right_part)
那么,我们已经将 { A , B } \{\text{A}, \text{B}\} {A,B} 中的所有元素划分为相同长度的两个部分,且其中一部分中的元素总是大于另一部分中的元素。那么:
m e d i a n = m a x ( left_part ) + m i n ( right_part ) 2 median = \frac{max(\text{left\_part}) + min(\text{right\_part})}2 median=2max(left_part)+min(right_part)
要确保这两个条件,我们只需要保证:
-
i + j = m − i + n i+j = m - i + n i+j=m−i+n或者 m − i + n − j + 1 m - i + n - j + 1 m−i+n−j+1
-
B ∣ j − 1 ∣ ≤ A [ i ] B|j - 1| ≤ A[i] B∣j−1∣≤A[i]以及 A [ i − 1 ] ≤ B [ j ] A[i−1]≤B[j] A[i−1]≤B[j]
-
为了简化分析,我假设 A [ i − 1 ] , B [ j − 1 ] , A [ i ] , B [ j ] \text{A}[i-1], \text{B}[j-1], \text{A}[i], \text{B}[j] A[i−1],B[j−1],A[i],B[j]总是存在,哪怕出现 i = 0 i=0 i=0, i = m i=m i=m, j = 0 j=0 j=0,或是 j = n j=n j=n 这样的临界条件。我将在最后讨论如何处理这些临界值。
-
为什么 n > m ? n > m? n>m?由于$0 \leq i \leq m0≤i≤m $ 且 j = m + n + 1 2 − i j = \frac{m + n + 1}2 - i j=2m+n+1−i,我必须确保 j j j 不是负数。如果 n < m n < m n<m 那么 j j j 将可能是负数,而这会造成错误的答案。
因此我们接下来要处理的事情就是:
在
[
0
,
m
]
[0,m]
[0,m] 中搜索并找到目标对象
i
i
i,以达到:
B
[
j
−
1
]
≤
A
[
i
]
B[j−1]≤A[i]
B[j−1]≤A[i] 且
A
[
i
−
1
]
≤
B
[
j
]
\text{A}[i-1] \leq \text{B}[j]
A[i−1]≤B[j], 其中
j
=
m
+
n
+
1
2
−
i
j = \frac{m + n + 1}{2} - i
j=2m+n+1−i
接着,我们可以按照以下步骤来进行二叉树搜索:
-
设 imin = 0 \text{imin} = 0 imin=0, imax = m \text{imax} = m imax=m, 然后开始在 [ imin , imax ] [\text{imin}, \text{imax}] [imin,imax] 中进行搜索。
-
i = imin + imax 2 i = \frac{\text{imin} + \text{imax}}{2} i=2imin+imax
-
现在我们有 len ( left _ part ) = len ( right _ part ) \text{len}(\text{left}\_\text{part})=\text{len}(\text{right}\_\text{part}) len(left_part)=len(right_part)。 而且我们只会遇到三种情况:
- B [ j − 1 ] ≤ A [ i ] 且 A [ i − 1 ] ≤ B [ j ] B[j−1]≤A[i] 且 \text{A}[i-1] \leq \text{B}[j] B[j−1]≤A[i]且A[i−1]≤B[j]:这意味着我们找到了目标对象 ii,所以可以停止搜索。
-
B
[
j
−
1
]
>
A
[
i
]
B[j−1]>A[i]
B[j−1]>A[i]:这意味着
A
[
i
]
\text{A}[i]
A[i] 太小,我们必须调整
i
i
i 以使
B
[
j
−
1
]
≤
A
[
i
]
\text{B}[j-1] \leq \text{A}[i]
B[j−1]≤A[i]。
我们可以增大 i i i 吗?
是的,因为当 i i i 被增大的时候, j j j 就会被减小。
因此 B [ j − 1 ] \text{B}[j-1] B[j−1] 会减小,而 A [ i ] \text{A}[i] A[i] 会增大,那么 \text{B}[j-1] \leq \text{A}[i]B[j−1]≤A[i] 就可能被满足。
我们可以减小 i i i 吗?
不行,因为当 i i i 被减小的时候, j j j 就会被增大。
因此 B [ j − 1 ] \text{B}[j-1] B[j−1] 会增大,而 A [ i ] \text{A}[i] A[i] 会减小,那么 B [ j − 1 ] ≤ A [ i ] \text{B}[j-1] \leq \text{A}[i] B[j−1]≤A[i] 就可能不满足。
所以我们必须增大 i i i。也就是说,我们必须将搜索范围调整为 [ i + 1 , imax ] [i+1, \text{imax}] [i+1,imax]。因此,设 imin = i + 1 \text{imin} = i+1 imin=i+1,并转到步骤 2。 -
A
[
i
−
1
]
>
B
[
j
]
A[i−1]>B[j]
A[i−1]>B[j]:
这意味着 A [ i − 1 ] \text{A}[i-1] A[i−1] 太大,我们必须减小 i i i 以使 A [ i − 1 ] ≤ B [ j ] \text{A}[i-1]\leq \text{B}[j] A[i−1]≤B[j]。
也就是说,我们必须将搜索范围调整为 [ imin , i − 1 ] [\text{imin}, i-1] [imin,i−1]。
因此,设 imax = i − 1 \text{imax} = i-1 imax=i−1,并转到步骤 2。
当找到目标对象 i i i 时,中位数为:
-
m a x ( A [ i − 1 ] , B [ j − 1 ] ) max(A[i−1],B[j−1]) max(A[i−1],B[j−1]), 当 m + n m + n m+n为奇数时
-
max ( A [ i − 1 ] , B [ j − 1 ] ) + min ( A [ i ] , B [ j ] ) 2 , \frac{\max(\text{A}[i-1], \text{B}[j-1]) + \min(\text{A}[i], \text{B}[j])}{2}, 2max(A[i−1],B[j−1])+min(A[i],B[j]), , 当 m + n m + n m+n 为偶数时
现在,让我们来考虑这些临界值 i = 0 , i = m , j = 0 , j = n i=0,i=m,j=0,j=n i=0,i=m,j=0,j=n,此时$ \text{A}[i-1],\text{B}[j-1],\text{A}[i],\text{B}[j]$ 可能不存在。其实这种情况比你想象的要容易得多。
我们需要做的是确保 max ( left _ part ) ≤ min ( right _ part ) \text{max}(\text{left}\_\text{part}) \leq \text{min}(\text{right}\_\text{part}) max(left_part)≤min(right_part)。 因此,如果 ii 和 jj 不是临界值(这意味着 A [ i − 1 ] , B [ j − 1 ] , A [ i ] , B [ j ] \text{A}[i-1], \text{B}[j-1],\text{A}[i],\text{B}[j] A[i−1],B[j−1],A[i],B[j]全部存在), 那么我们必须同时检查 B [ j − 1 ] ≤ A [ i ] \text{B}[j-1] \leq \text{A}[i] B[j−1]≤A[i] 以及 A [ i − 1 ] ≤ B [ j ] \text{A}[i-1] \leq \text{B}[j] A[i−1]≤B[j] 是否成立。
但是如果
A
[
i
−
1
]
,
B
[
j
−
1
]
,
A
[
i
]
,
B
[
j
]
\text{A}[i-1],\text{B}[j-1],\text{A}[i],\text{B}[j]
A[i−1],B[j−1],A[i],B[j]中部分不存在,那么我们只需要检查这两个条件中的一个(或不需要检查)。
举个例子,如果
i
=
0
i = 0
i=0,那么
A
[
i
−
1
]
\text{A}[i-1]
A[i−1] 不存在,我们就不需要检查
A
[
i
−
1
]
≤
B
[
j
]
\text{A}[i-1] \leq \text{B}[j]
A[i−1]≤B[j] 是否成立。
所以,我们需要做的是:
在 [0,m][0,m] 中搜索并找到目标对象 ii,以使:
- ( j = 0 o r i = m o r B [ j − 1 ] ≤ A [ i ] ) (j = 0 or i = m or \text{B}[j-1] \leq \text{A}[i]) (j=0ori=morB[j−1]≤A[i]) 或是
- ( i = 0 o r j = n o r A [ i − 1 ] ≤ B [ j ] ) (i = 0 or j = n or \text{A}[i-1] \leq \text{B}[j]) (i=0orj=norA[i−1]≤B[j]), 其中 j = m + n + 1 2 − i j = \frac{m + n + 1}{2} - i j=2m+n+1−i
在循环搜索中,我们只会遇到三种情况:
- ( j = 0 o r i = m o r B [ j − 1 ] ≤ A [ i ] ) (j=0 or i = m or \text{B}[j-1] \leq \text{A}[i]) (j=0ori=morB[j−1]≤A[i])或是 ( i = 0 o r j = n o r A [ i − 1 ] ≤ B [ j ] ) (i = 0 or j = n or \text{A}[i-1] \leq \text{B}[j]) (i=0orj=norA[i−1]≤B[j]),这意味着 i i i 是完美的,我们可以停止搜索。
- j > 0 a n d i < m a n d B [ j − 1 ] > A [ i ] j>0 and i < m and \text{B}[j - 1] > \text{A}[i] j>0andi<mandB[j−1]>A[i] 这意味着 i i i 太小,我们必须增大它。
- i > 0 a n d j < n a n d A [ i − 1 ] > B [ j ] i>0 and j < n and \text{A}[i - 1] > \text{B}[j] i>0andj<nandA[i−1]>B[j]这意味着 i i i 太大,我们必须减小它。
i < m ⟹ j > 0 i < m \implies j > 0 i<m⟹j>0 以及 i > 0 ⟹ j < n i > 0 \implies j < n 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<m \implies j = \frac{m+n+1}{2} - i > \frac{m+n+1}{2} - m ≥ \frac{2m+1}{2} - m ≥ 0
m≤n,i<m⟹j=2m+n+1−i>2m+n+1−m≥22m+1−m≥0
m
≤
n
,
i
>
0
⟹
j
=
m
+
n
+
1
2
−
i
<
m
+
n
+
1
2
≤
2
n
+
1
2
≤
n
m≤n, i>0 \implies j = \frac{m+n+1}{2} - i < \frac{m+n+1}{2} ≤ \frac{2n+1}{2} ≤ n
m≤n,i>0⟹j=2m+n+1−i<2m+n+1≤22n+1≤n
所以,在情况 2 和 3中,我们不需要检查 j > 0 j > 0 j>0 或是 j < n j < n j<n 是否成立。
复杂度分析
- 时间复杂度:
O
(
log
(
min
(
m
,
n
)
)
)
O\big(\log\big(\text{min}(m,n)\big)\big)
O(log(min(m,n))),
首先,查找的区间是 [ 0 , m ] [0, m] [0,m]。
而该区间的长度在每次循环之后都会减少为原来的一半。
所以,我们只需要执行 log ( m ) \log(m) log(m) 次循环。由于我们在每次循环中进行常量次数的操作,所以时间复杂度为 O ( log ( m ) ) O\big(\log(m)\big) O(log(m))。
由于 m ≤ n m \leq n m≤n,所以时间复杂度是 O ( log ( min ( m , n ) ) ) O\big(\log\big(\text{min}(m,n)\big)\big) O(log(min(m,n)))。 - 空间复杂度:
O
(
1
)
O(1)
O(1),
我们只需要恒定的内存来存储 99 个局部变量, 所以空间复杂度为 O ( 1 ) O(1) O(1)。
C++代码
class Solution
{
public:
double findMedianSortedArrays(vector<int> &nums1, vector<int> &nums2)
{
int nums1Size = int(nums1.size());
int nums2Size = int(nums2.size());
//确保数组1是较短的数组
if (nums1Size > nums2Size)
{
return findMedianSortedArrays(nums2, nums1);
}
// Ci 为第i个数组的割,比如C1为2时表示第1个数组只有2个元素。lMaxi为第i个数组割后的左元素。rMini为第i个数组割后的右元素。
int lMax1, lMax2, rMin1, rMin2, c1, c2, lo = 0, hi = 2 * nums1Size; //我们目前是虚拟加了'#'所以数组1是2*n长度
while (lo <= hi)
{ //二分法
c1 = (lo + hi) / 2;
c2 = nums1Size + nums2Size - c1;
lMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2];
rMin1 = (c1 == 2 * nums1Size) ? INT_MAX : nums1[c1 / 2];
lMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2];
rMin2 = (c2 == 2 * nums2Size) ? INT_MAX : nums2[c2 / 2];
if (lMax1 > rMin2)
{
hi = c1 - 1;
}
else if (lMax2 > rMin1)
{
lo = c1 + 1;
}
else
{
break;
}
}
return (max(lMax1, lMax2) + min(rMin1, rMin2)) / 2.0;
}
};
Golang代码
// Solution by Panda.
// 生成一个新的数组,然后判断长度奇偶数,取中间值。
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
nums := combine(nums1, nums2)
return medianOf(nums)
}
func combine(mis, njs []int) []int {
lenMis, i := len(mis), 0
lenNjs, j := len(njs), 0
res := make([]int, lenMis+lenNjs)
for k := 0; k < lenMis+lenNjs; k++ {
if i == lenMis ||
(i < lenMis && j < lenNjs && mis[i] > njs[j]) {
res[k] = njs[j]
j++
continue
}
if j == lenNjs ||
(i < lenMis && j < lenNjs && mis[i] <= njs[j]) {
res[k] = mis[i]
i++
}
}
return res
}
func medianOf(nums []int) float64 {
l := len(nums)
if l == 0 {
panic("切片长度为0, 无法求解中位数.")
}
if l%2 == 0 {
return float64(nums[l/2]+nums[l/2-1]) / 2.0
}
return float64(nums[l/2])
}
Java代码
class Solution {
public double findMedianSortedArrays(int[] A, int[] B) {
int m = A.length;
int n = B.length;
if (m > n) { // to ensure m<=n
int[] temp = A; A = B; B = temp;
int tmp = m; m = n; n = tmp;
}
int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
while (iMin <= iMax) {
int i = (iMin + iMax) / 2;
int j = halfLen - i;
if (i < iMax && B[j-1] > A[i]){
iMin = i + 1; // i is too small
}
else if (i > iMin && A[i-1] > B[j]) {
iMax = i - 1; // i is too big
}
else { // i is perfect
int maxLeft = 0;
if (i == 0) { maxLeft = B[j-1]; }
else if (j == 0) { maxLeft = A[i-1]; }
else { maxLeft = Math.max(A[i-1], B[j-1]); }
if ( (m + n) % 2 == 1 ) { return maxLeft; }
int minRight = 0;
if (i == m) { minRight = B[j]; }
else if (j == n) { minRight = A[i]; }
else { minRight = Math.min(B[j], A[i]); }
return (maxLeft + minRight) / 2.0;
}
}
return 0.0;
}
}
成长,就是一个不动声色的过程,一个人熬过一些苦,才能无所不能。