题目描述
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的中位数。
示例 1
输入:nums1 = [1,3], nums2 = [2] 输出:2.00000 解释:合并数组 = [1,2,3] ,中位数 2
示例 2
输入:nums1 = [1,2], nums2 = [3,4] 输出:2.50000 解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
示例 3
输入:nums1 = [0,0], nums2 = [0,0] 输出:0.00000
示例 4
输入:nums1 = [], nums2 = [1] 输出:1.00000
示例 5
输入:nums1 = [2], nums2 = [] 输出:2.00000
解答
方法 1
给定两个正序的数组,要求返回两个正序数组的中位数,最容易想到的方法就是先将两个数组进行合并,然后再返回合并后的大数组的中位数即可。我们的第一种方法,也是用到数组合并的思想,但并不是完全合并两个数组。
两个数组的长度分别为 m 和 n,总长度 total_len = m + n,当 total_len 为奇数时,中位数的下标 mid 为 total_len // 2;为偶数时,mid = total_len / 2,此时中位数为 mid-1 和 mid 的下标对应值之和再除以 2。
先定义一个空的列表 new_arry。i,j 分别对应两个数组对应的下标,并比较 nums1[i] 和 nums2[j] 的大小,将值小的插入到 new_arry 中。
当 new_arry 的长度等于 mid 时,循环停止。
如果 total_len 为奇数,则返回 new_arry[mid] 即可;如果为偶数,则返回 (new_arry[mid-1] + new_arry[mid]) / 2.0。
class Solution: def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: res = 0.0 new_array = [] m, n = len(nums1), len(nums2) total_len = m + n if total_len % 2 != 0: mid = int(total_len//2) else: mid = int(total_len/2) i,j = 0,0 while len(new_array) -1 < mid: if i < m and j < n : if nums1[i] < nums2[j]: new_array.append(nums1[i]) i+=1 continue if nums1[i] >= nums2[j]: new_array.append(nums2[j]) j+=1 continue if i == m and j < n: new_array.append(nums2[j]) j+=1 if i < m and j == n: new_array.append(nums1[i]) i+=1 if total_len %2 != 0: res = new_array[mid] else: res = (new_array[mid-1] + new_array[mid])/2.0 return res
方法 2
中位数是一个数组中间位置的数,其实中位数把一个升序数组分成了长度相等的两部分,其中左半部分的最大值永远小于或等于右半部分的最小值。我们用一个例子来说明,比如现在有如下数组:
[1,2,3,5,6,7,7,8,9,15] [1,2,3,5,6] < [7,7,8,9,15]
如上所示,对于偶数长度的数组,可以根据中位数分成长度相等的两部分,左半部分的最大元素(6),永远小于或等于右半部分的最小元素(7)。
对于奇数长度的数组,同样可以根据中位数分成两部分:
[1,2,3,4,5,5,6,7,9,10,11] [1,2,3,4,5,5] < [6,7,9,10,11]
如上所示,对于奇数长度的数组,如果把中位数本身归入左半部分,则左半部分长度 = 右半部分长度 + 1。
左半部分的最大元素(5),永远小于或等于右半部分的最小元素(6)。
大数组被中位数等分的左右两部分,每一部分根据来源又可以再划分成两部分,其中一部分来自数组 nums1 的元素,另一部分来自数组 nums2 的元素:
数组 nums1 数组 nums2 [3,5,7,8,9] < [1,2,6,7,15] i j [1,2,3,5,6,7,7,8,9,15] 大数组 i+j
如上所示,原始数组 nums1 和数组 nums2,各自分成黑色和橙色两部分。其中数值较小的黑色元素组成了大数组的左半部分,数组较大的橙色元素组成了大数组的右半部分。
最重要的是,黑色元素和橙色元素的数量是相等的(偶数情况),而且最大的黑色元素小于或等于最小的橙色元素。
假设数组 nums1 的长度是 m,黑色和橙色元素的分界点是 i,数组 nums2 的长度是 n,黑色和橙色元素的分界点是 j,那么为了让大数组的左右两部分相等,则 i 和 j 需要符合如下条件: i + j = (m + n + 1) / 2(之所以 m + n 后面要再加 1,是为了应对大数组长度为奇数的情况),max(nums1[i-1], nums2[j-1])<= min(nums1[i], nums2[j])(直白地说,就是最大的黑色元素小于或等于最小的橙色元素)。
由于 m + n 的值是恒定的,所以我们只要确定一个合适的 i,就可以确定 j,从而找到大数组左半部分和右半部分的分界,也就找到了归并之后大数组的中位数。
对于这个合适的 i 值,我们采用二分查找的思路来寻找。下面通过一个具体的例子来看看二分查找确定 i 值:
数组 nums1:[3,5, 6, 7, 8, 15,20] 数组 nums2:[1,10,12,18,21,24,25,27]
第一步,就像二分查找那样,把 i 设在数组 nums1 的正中位置,也就是让 i = 3;
数组 nums1:[3,5, 6, 7, 8, 15,20]
i 数组 nums2:[1,10,12,18,21,24,25,27]
第二步,根据 i 的值来确定 j 的值,j = (m + n + 1) / 2 - i = 5;
数组 nums1:[3,5, 6, 7, 8, 15,20] i 数组 nums2:[1,10,12,18,21,24,25,27]
j
第三步,验证 i 和 j,分为下面三种情况:
1. nums2[j-1] <= nums1[i] && nums1[i-1] <= nums2[j]
说明 i 和 j 左侧的元素都小于或等于右侧的元素,这一组 i 和 j 是我们想要的。
2. nums1[i] < nums2[j-1]
说明 i 对应的元素偏小了,i 应该向右侧移动。
3. nums1[i-1] > nums2[j]
说明 i-1 对应的元素偏大了,i 应该向左侧移动。
显然,上面的例子中属于情况 2,nums1[3] < nums2[5],所以 i 应该向右移动。
第四步,在数组 nums1 的右半部分,重新确定 i 的位置,就像二分查找一样:
数组 nums1:[3,5, 6, 7, 8, 15,20] i 数组 nums2:[1,10,12,18,21,24,25,27]
第五步,同第二步,根据 i 来确定 j 的值,j = (m + n + 1) / 2 - i = 3:
数组 nums1:[3,5, 6, 7, 8, 15,20] i 数组 nums2:[1,10,12,18,21,24,25,27] j
第六步,同第三步,验证 i 和 j:
由于nums1[5] >= nums2[2] 且 nums2[3] >= nums1[4],所以这一组 i 和 j 是合适的。
第七步,找出中位数:
如果大数组的长度是奇数,那么:中位数 = max(nums1[i-1], nums2[j-1])(也就是大数组左半部分的最大值)。
如果大数组的长度是偶数,那么:中位数 = (max(nums1[i-1], nums2[j-1]) + min(nums1[i], nums2[j])) / 2(也就是大数组左半部分的最大值和大数组右半部分的最小值取平均)。
对于一些特殊情况
1. 数组 nums1 的长度大于数组 nums2
提前把数组 nums1 和数组 nums2 进行交换,较短的数组放在前面,i 从较短的数组中取。这样做还有一个好处,由于数组 nums1 是较短数组,i 的搜索次数减少了。
2. 无法找到合适的 i 值
什么情况下会无法找到合适的 i 值呢?有两种情况:
数组 nums1 的长度小于数组 nums2 的长度,并且数组 nums1 的所有元素都大于数组 nums2 的元素。
在这种情况下,无法通过二分查找寻找符合 nums2[j-1] <= nums1[i] && nums1[i-1] <= nums2[j] 的 i 值,一直到 i=0 为止。此时我们可以跳出二分查找的循环,所求的中位数是 nums2[j-1]。(仅限奇数情况)
数组 nums1 的长度小于数组 nums2 的长度,并且数组 nums1 的所有元素都小于数组 nums2 的元素
在这种情况下,同样无法通过二分查找寻找符合 nums2[j-1] <= nums1[i] && nums1[i-1] <= nums2[j] 的 i 值,一直到 i=数组 nums1 的长度-1 为止。此时我们可以跳出二分查找的循环,所求的中位数是 max(nums1[i-1], nums2[j-1])。(仅限奇数情况)
class Solution: def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: m, n = len(nums1), len(nums2) if m>n: nums1, nums2, m, n=nums2, nums1, n, m start, end, half_len = 0, m, (m + n + 1)//2 while start<=end: i = (start+end)//2 j = half_len - i if i < m and nums2[j-1] > nums1[i]: start = i + 1 elif i > 0 and nums1[i-1] > nums2[j]: end = i - 1 else: if i == 0: max_of_left = nums2[j-1] elif j == 0: max_of_left = nums1[i-1] else: max_of_left = max(nums1[i-1], nums2[j-1]) if (m + n)%2 == 1: return max_of_left if i == m: min_of_right = nums2[j] elif j == n: min_of_right = nums1[i] else: min_of_right = min(nums1[i], nums2[j]) return (max_of_left + min_of_right)/2.0