寻找中位数
中位数概念
一组有序数列中,居于中间位置的那个数,代表一个样本或概率分布的一个数值。譬如[1, 2, 3, 4, 5]
的中位数为3,[1, 2, 3, 4, 5, 6]
这个数列中居于中间的数字有两个3
和4
,那么中位数应该是3.5
(3和4的平均值).
题目
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
1 | nums1 = [1, 3] |
则中位数是 2.0
示例 2:
1 | nums1 = [1, 2] |
则中位数是 (2 + 3)/2 = 2.5
暴力解决
采用类似归并排序的思想,从两个有序数列的最小值开始进行比较排序,一直排到中位数附近。
需考虑特殊情况:
m + n
的和为基数还是偶数;- 其中一个数列为空;
- 其中一个数列先排完;
时间复杂度为O(m + n)
,空间复杂度为O(1)
1 | double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size){ |
时间复杂度为O(m + n)
,空间复杂度为O(1)
二分查找
二分法查找第n大的数
- 对于一个有序数组来说,其中位数始终可以用
mid = (nums((n + 1)/2) + nums((n + 2)))/2
的公式求出 - 那么对于两个有序数组来说,我们只要找出两个数组集合中第
(m+n+1)/2
大的数和第(m+n+2)/2
大的数,然后求平均数即可。注意这里的第(m+n+1)/2大的数中m和n分别指两个数组的大小; - 所以对于本题,也就转换为了找出两个数组集合中第
k
大的数,而我们采用类似二分查找的方法来找到这个数。
首先来看看对于两个数组nums1: [a1, a2, ..., a(k/2), ...]
nums2: [b1, b2, ..., b(k/2), ...]
如果a(k/2) < b(k/2)
, 考虑两种极端情况:
b(k/2)
前所有的数b1, ... b(k/2-1)
都大于a(k/2)
,也就是说比a(k/2)
小的数只有a(k/2)
之前的数,也就是a1, ..., a(k/2-1)
, 那么a(k/2)
也就是nums1
和nums2
两个数组中第k/2
大的数;b(k/2)
前所有的数b1, ... b(k/2-1)
都小于a(k/2)
,也就是说比a(k/2)
小的数只有a(k/2)
之前的数(a1, ..., a(k/2-1)
)和b(k/2)
之前的数(b1, ... b(k/2-1)
),那么a(k/2)
也就是nums1
和nums2
两个数组中第k/2-1 + k/2-1 + 1= k-1
大的数;
所以,在a(k/2) < b(k/2)
这个条件下,a(k/2)
在两个数组中最小是第k/2
大的数,最大是k-1
大的数,也就是第k
大的数永远不可能在nums1中a(k/2)
之前的数中产生,那么我们把a1, ..., a(k/2-1)
丢弃掉,在剩余的数中继续找第k-(k/2-1)=k/2+1
(代码实现中为k-k/2=
k/2是因为代码中为下标运算),一直递归下去一直到当
k=1或者其中一个数组长度为
0`了,然后结束。
- 当
k = 1
, 即找两个数组中第1
大的数,那么只需比较两个有序数组的起始元素; - 当其中一个数据长度为
0
了,只需去另一个数组中找到第k
大的数即可; - 我们每次都是取
k/2
的数进行比较,有时候可能会遇到数组长度小于k/2
的时候,即nums1: [a1, a2] 2 < k/2, num2: [b1, ..., b(k/2), ...],
这时候只需要舍弃另一个数组中k/2
前的数即b1, ..., b(k/2-1)
就行了(2 < k/2, k/2-1 + 2 < k/2-1 + k/2 = k - 1
)
1 |
|
时间复杂度:每进行一次循环,我们就减少 k/2 个元素,所以时间复杂度是 O(log(k),而 k=(m+n)/2,所以最终的复杂也就是 O(log(m+n)。
空间复杂度为O(1)。
二分法切割
让我们在上一个算法的基础上再看看。nums1: [a1, a2, ..., a(k/2), a(k/2+1), ..., a(m)]
nums2: [b1, b2, ..., b(k/2), b(k/2+1), ..., b(n)]
如果a[k/2] <= b[k/2]
并且b[k/2] <= a[k/2]
,也就是意味着[a1, ..., a(k/2)
, b1, …, b(k/2)]中的数始终不可能比
[a(k/2+1), …, a(m), b(k/2), …, b(n)]中的数大,那么第
k大的数则为
a(k/2)`、b(k/2)中比较大的那一个。
那么在本题中,我们要找的是第(m+n+1)/2
大的数,来来来,转换一下,假设我们对num1
进行切割,切割点为i
,对nums2
的切割点为(m+n+1)/2-i
;nums1: [a1, a2, ..., a(i), a(i+1), ..., a(m)]
nums2: [b1, b2, ..., b((m+n+1)/2-i), b((m+n+1)/2-i+1), ..., b(n)]
- 当
a(i) < b((m+n+1)/2-i+1)
并且b((m+n+1)/2-i) < a(i+1)
时,中位数为max(a(i), b((m+n+1)/2-i)
(m+n为奇数),或者(max(a(i), b((m+n+1)/2-i)) + min(a(i+1), b((n+1)/2-i+1)))/2
(m+n为偶数) - 所以我们只需要在
nums1
数组中找出一个切割点i
,使得a(i) < b((m+n+1)/2-i+1)
并且b((m+n+1)/2-i) < a(i+1)
,就可以找到我们所需要的中位数 - 那么怎么样从
nums1
中找到这个切割点呢,我们这里采用二分法来进行切割,首先让i=m/2
, 如果a(i) > b((m+n+1)/2-i+1)
,则从a(i)
的左边进行二分切割,如果a(i+1) < b((m+n+1)/2-i)
,则从a(i)
的左边进行二分切割,一直切割到满足条件为止;
当然这里面要考虑到特殊情况,如果切割点到头了怎么办,也就是:
- 当
i = 0
时,说明nums1
中所有数都大于中位数,则中位数为b((m+n+1)/2-m0
(m+n
为奇数), 或(b((m+n+1)/2-0) + min(a1 , b((m+n+1)/2-0+1)))/2.0
(m+n
为偶数数); - 当
i = m
时,说明nums1
中所有数都小于总位数,则中位数为min(am, b((m+n+1)/2-m))
(m+n
为奇数),或(min(am, b((m+n+1)/2-m)) + b((m+n+1)/2-m+1))/2.0
(m+n
为偶数数)
1 |
|