这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战
题目
4. 寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 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
提示:
nums1.length == mnums2.length == n0 <= m <= 10000 <= n <= 10001 <= m + n <= 2000-106 <= nums1[i], nums2[i] <= 106
思路
常规做法我们可以用归并排序,来合并两个有序的数组,然后判断数组的长度,如果数组长度是奇数,我们就直接返回最中间的元素,如果数组长度是偶数,我们就返回中间两个元素的平均值即可。但是这道题要求我们时间复杂度压在O(log (m+n))内,说明不想让我们合并数组,得直接用二分查找去找出我们指定的位置
- 首先我们可以根据两个数组的长度来确定中位数的值的索引
k- 如果两数组相加的和是奇数,说明中位数刚好是排在中间的一个数
- 如果两数组相加的和是偶数,说明中位数是排在中间的两个数的平均值
- 然后我们可以对要查找的K个元素做一次二分查找,每次从两个数组中分别取出
k/2个元素,比较它们最后一个元素的大小; - 接着把小的那部分元素当作我们查找出来的第
k/2小的元素,k每次减少一小半,记得如果k是7,只减去3 - 这样子递归执行直到k的值为1的时候,比较两个数组的第一个元素即可
- 当然,中间我们需要做特殊处理,如果一个数组的值不足这个
k/2时候,我们可以把多余部分从另一个数组中直接减去即可。
举个例子:
我们要从两个数组[1, 3, 5, 7] 和[2, 4, 6]中找到中位数
我们会先找到中位数是第几个元素: (4 + 3 >> 1) + 1 ==> 4
PS: 4 + 3 >> 1 等同于 Math.floor((4 + 3) / 2)
也就是我们要找到所有数据中排在第4大的元素,那么我们可以从两边各取两个元素
[1, 3] 和 [2, 4] 比较它们末尾元素的大小,因为 3 < 4, 所以在这两个升序数组中,前面的4个最小的数一定包含[1, 3]。
接着我们只需要找出两个数组中前面2个最小的数即可,我们要找k个数, 那么我们每次就从数组中各自找出k / 2部分,假设一人出一半,然后把小的那一截放进去即可。
当然,如果一个数组是空的怎么办,我们可以在循环的时候加个判断,如果数据中的元素不够k / 2个,那么不够的那部分直接从另一个数组中取即可。
实现
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number}
*/
function findMedianSortedArrays(nums1, nums2) {
const m = nums1.length, n = nums2.length;
// 实际元素比索引多1,所以要 +1
const mid = (m + n >> 1) + 1;
if ((m + n) % 2 === 1) {
return findKthNum(nums1, nums2, mid);
} else {
return (findKthNum(nums1, nums2, mid) + findKthNum(nums1, nums2, mid - 1)) / 2;
}
}
function findKthNum(nums1, nums2, k) {
const m = nums1.length, n = nums2.length;
let left1 = left2 = 0;
// 一直二分查找,直到k为1的时候,比较两者的最小值即可
while (k > 1 && left1 < m && left2 < n) {
const mid = k >> 1;
const index1 = left1 + mid - 1,
index2 = left2 + mid - 1;
// 如果两边值都够的话
if (index1 < m && index2 < n) {
if (nums1[index1] < nums2[index2]) {
left1 = index1 + 1;
} else {
left2 = index2 + 1;
}
k -= mid;
} else {
// 把双方拉到同一起跑线再比较
const min = Math.min(m - left1, n - left2);
if (m - left1 === min) {
left2 += k - 2 * min;
} else {
left1 += k - 2 * min;
}
k = 2 * min;
}
}
// 如果有一个到尽头了
if (left1 === m) {
return nums2[left2 + k - 1];
}
if (left2 === n) {
return nums1[left1 + k - 1];
}
// 返回两个数组的最小元素
return Math.min(nums1[left1 + k - 1], nums2[left2 + k - 1]);
}
看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。