寻找两个有序数组的中位数
Median of Two Sorted Arrays
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)).
You may assume nums1 and nums2 cannot be both empty.
Example 1:
1 2
| nums1 = [1, 3] nums2 = [2]
|
The median is 2.0
Example 2:
1 2
| nums1 = [1, 2] nums2 = [3, 4]
|
The median is (2 + 3)/2 = 2.5
题目大意
“中位数”的作用是什么?
将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。
其中有序数组又分为:
- 奇数组:
[2 3 4]
对应的中位数为 3
- 偶数组:
[1 5 7 9]
对应的中位数为 (5 + 7) / 2 = 6
解题思路
切割数组
通过切割将有序数组分割为左右两部分, 两部分长度一致
,且分割的位置,左侧为左部分最大值 MaxL=Max(LeftPart)
MinR=Min(RightPart)如上述奇数组和偶数组所示,切割的位置可以为一个数(3),此时此数同时属于左右两部分,也可以为两个数中间, 此时取平均数。
- 奇数组:
[2 3 4]
中位数为 3
,此时切割位置为数字3
,将数组分为左右两部分,此时数组等同于[2 (3/3) 4]
,所以 MaxL=3,MinR=3 - 偶数组:
[1 5 7 9]
中位数为 6
,此时切割的位置为数字5
与7
之间,将数组分为左右两部分,此时数组等同于[1 (5/7) 9]
,所以MaxL=5,MinR=7
切割第k个元素
单个数组
对于一个有序数组A
,如果在位置k
处切割恰好能将其分割成左右两部分长度一致的Slice
(切割点落在数字上,而不是两个数之间),那么此时 MaxL=MinR=A(k)
多个数组
如题所示,分别切割两个有序数组,分割出的左侧与右侧长度一致,定义切割位置为第k
位的元素
Si 为切割位置元素 MaxLi 为左侧部分第`i`位最大元素 MinRi 为右侧部分第`i`位最小元素
因为数组为有序数组,所以,MaxALi<=MinARi,MaxBLi<=MinBRi,而如果切割点在某个数上,则两边相等。
如果使得MaxALi<=MinBRi,MaxBLi<=MinBRi

如果 LeftPart
全部小于RightPart
,如果左边的元素个数为 k
,那么第 k
个元素为 Max(MaxLa,MaxLb)
而如果 MaxALi>>MinBRi,则说明左侧元素远大于右侧,将 Sai减小,则 Sbi=k−Sai 增大;相反 MaxBLi>>MinARi,将Sbi减小,相应的Sai=k−Sbi也增大。
假设切割元素k=3
对于有序数组 [2 3 4]
、[1 5 7 9]
设Sai=1,那么Sbi=k−Sai=2,即 [2 / 3 4]
、[1 5 / 7 9]
,此时MaxALi=2,MinARi=3;
MaxBLi=5,MinBRi=7;此时MaxBLi>MinARi,需要将Sbi减小,Sai增大,让Sai=2,此时有序数组分割为: [2 3 / 4]
、[1 / 5 7 9]
,此时
MaxALi=3,MinARi=4; MaxBLi=1,MinBRi=5;满足MaxALi<MinBRi且MaxBLi<MinARi,所以第 k(3)
个元素为Max(MaxALi,MaxBLi)=3
转换数组
为了将数组中位数计算方式统一(计算左边最大值与右边最小值的平均值,切割点为两数之间而不是数字上),对数组进行如下填充:
元数组 | Lenbefore | 处理数组 | Lenafter |
---|
[2 3 4] | 3 | [* 2 * 3 * 4 *] | 7 |
[1 5 7 9] | 4 | [* 1 * 5 * 7 * 9 *] | 9 |
将数组元素前后位置用*
进行填充,使得原数组A
长度由m
变为2m+1
,原数组B
长度由n
变为2n+1
,两个数组总长度为2m+2n+2
,数组总长度总是为偶数
,而偶数长度数组切分方式为:两边长度一致,取左边最大值与右边最小值的平均数。
原元素位置 = 现元素位置/2 取整,;例如元素2,原索引为0,现索引为1,则 1/2=0
;元素7,原索引为2,现索引为5,则 5/2=2
对于切割点Si则有MaxLi=(Si−1)/2,MinRi=Si/2 位置上的元素。
验证
对于数组A [2 3 4]
,切割点为 3
,则 Si=3,
MaxLi=A[(3−1)/2]=A[1]=3, MinRi=A[3/2]=A[1]=3对于数组B [1 5 7 9]
, 切割点在数字 5
与7
之间的*
,则 Si=4,
MaxLi=B[(4−1)/2]=A[1]=5, MinRi=B[4/2]=A[2]=7将两个数组看作一个整体S
,则数组总长度为 2m+2n+2
,切割点为 m+n+1
,所以计算 m+n+1
以及m+n+2
元素就可以得到中位数。
左侧部分:S[m+n+1]=Max(MaxALi,MaxBLi)
右侧部分:S[m+n+2]=Min(MinARi,MinBRi)
中位数:Smid=(S[m+n+1]+S[m+n+2])/2=(Max(MaxALi,MaxBLi)+Min(MinARi,MinBRi))/2
综合
使用二分法对长度较短的数组进行切分,得到 Sai 则 Sbi 也确定了。
- MaxALi>MinBRi,则 Sai 减小,Sbi 增大,切割点在数组`A`左移进行二分
- MaxBLi>MinARi,则 Sai 增大,Sbi 减小,切割点在数组`A`右移进行二分
如果有个数组完全小于或大于中值,此时 n < m
,出现以下几种情况:
- Sai=0 ,此时数组元素全部在右侧部分,Sbi=k−Sai 存在与数组 `B`中,假设 MaxALi = `INT_MIN`
- Sai=2m,此时数组元素全部在左侧部分,Sbi=k−Sai 存在与数组 `B`中,假设 MinALi = `INT_MAX`,此时 MaxBLi<<MinARi
- Sbi=0 ,此时数组元素全部在右侧部分,Sai=k−Sbi 存在与数组 `A`中,假设 MaxBLi = `INT_MIN`
- Sbi=2n,此时数组元素全部在左侧部分,Sai=k−Sbi 存在与数组 `A`中,假设 MinBRi = `INT_MAX`,此时 MaxALi<<MinBRi
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| func findMedianSortedArrays(nums1 []int, nums2 []int) float64 { var m, n = len(nums1), len(nums2) if m > n { return findMedianSortedArrays(nums2, nums1) } const ( INT_MAX = int(^uint(0) >> 1) INT_MIN = ^INT_MAX ) var MaxLA, MaxLB, MinRA, MinRB, S1, S2, lop int var hi = 2 * m + 1 for lop <= hi { S1 = (lop + hi) / 2; S2 = m + n - S1; if S1 == 0 { MaxLA = INT_MIN } else { MaxLA = nums1[(S1 - 1) / 2] } if S1 == 2 * m { MinRA = INT_MAX } else { MinRA = nums1[S1 / 2] } if S2 == 0 { MaxLB = INT_MIN } else { MaxLB = nums2[(S2 - 1) / 2] } if S2 == 2 * n { MinRB = INT_MAX } else { MinRB = nums2[S2 / 2] }
if MaxLA > MinRB { hi = S1 - 1 } else if MaxLB > MinRA { lop = S1 + 1 } else { break } } return float64(max(MaxLA, MaxLB) + min(MinRA, MinRB)) / 2.0 }
func max(a,b int) int { if a > b { return a } return b }
func min(a,b int) int { if a > b { return b } return a }
|
思路参考 bian-bian-xiong
题解