「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」
给定两个大小分别为 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
示例 3:
输入: nums1 = [0,0], nums2 = [0,0]
输出: 0.00000
示例 4:
输入: nums1 = [], nums2 = [1]
输出: 1.00000
示例 5:
输入: nums1 = [2], nums2 = []
输出: 2.00000
提示:
nums1.length == mnums2.length == n0 <= m <= 10000 <= n <= 10001 <= m + n <= 2000-106 <= nums1[i], nums2[i] <= 106
通过次数552,171提交次数1,347,470
本题需要知道的是如果输入两个数组的长度之和 len为奇数,则中位数为中间一个元素(下标为 len>>1 )的值,否则为中间两个元素 (下标为 len>>1 和 len>>1+1) 的平均值)
本题解题并不难,难点在于题解的时间复杂度应该为 O(log (m+n))
首先我们看一下时间复杂度没达到给定要求但是比较好理解的题解 => 题解1 & 题解2
题解1
解题思路如下:
- 通过结构赋值将输入数组合并为一个数组
- 使用
sort对合并数组进行排序 - 根据合并数组长度找到中位数返回
代码如下:
var findMedianSortedArrays = function(nums1, nums2) {
// 合并数组
const arr = [...nums1,...nums2],
// 获取数组长度
len = arr.length;
// sort 排序
arr.sort((a,b) => a-b)
// 获取中间下标
const midIndex = len>>1
// 判断数组长度奇偶返回对应结果
return len%2?arr[midIndex]:(arr[midIndex-1]+arr[midIndex])/2
}
因为 Array.sort 函数各浏览器实现并不相同,这里以 v8 为例,使用快排加插入排序,所以本题解时间复杂度可以定为 O((m+n)log (m+n))
题解2
解题思路如下:
- 获取两数组长度之和
len - 初始化一个数组
arr存放输入两数组排序后的结果 - 获取中位数下标
mid = len>>1(如果len为奇数,则中位数为arr[mid]的值,否则为(arr[mid-1]+arr[mid])/2)) - 初始化两个指针
tail1、tail2分别指向两个数组的下标0 - 每次将两指针指向数字中的最小值放入
arr,并将指针向后移动一位,直到arr.length>mid - 判断
len奇偶返回中位数
代码如下:
var findMedianSortedArrays = function(nums1, nums2) {
// 获取两数组长度
const len1 = nums1.length,
len2 = nums2.length,
// 获取两数组中长度
len = len1+len2,
// 初始化arr存放有序元素
arr = [],
// 获取第一个中位数下标
mid=len>>1;
// 初始化指针
let tail1 = tail2 = 0;
// 当两数组不为空,将两指针更小值放入arr
while(tail1<len1||tail2<len2){
if(tail1===len1){
arr.push(nums2[tail2]);
tail2++;
}else if(tail2===len2){
arr.push(nums1[tail1]);
tail1++;
}else if(nums1[tail1]>nums2[tail2]){
arr.push(nums2[tail2]);
tail2++;
}else{
arr.push(nums1[tail1]);
tail1++;
}
// 如果数组中有mid+1个元素的时候,即可返回结果
if(arr.length>mid){
if(len%2) return arr[mid]
return (arr[mid-1]+arr[mid])/2
}
}
}
该题解时间复杂度为 O((m+n)>>1+1)
题解3
通常在一个有序数组中查找某个值,又要求时间复杂度为 O(log (n)),就要用到 二分查找
此题一个复杂的点就是在两个有序数组中,找到第 k 大的值,我们一起来看一下该题如何利用 二分查找解题
解题思路如下:
- 编写一个函数在两个有序数组中获取第
k大的值 - 如果两数组长度之和
len为奇数,只需要找到第(len+1)>>1大的值即可,否则需要找到第(len+1)>>1及(len+1)>>1+1大的元素,返回它们的平均数即可
该题解解题思路很简单,难点在于如何利用 二分查找 在两个有序数组中查到第 k 大的元素
思路如下:
- 函数传入两个数组及两个数组的起始下标以及
k - 判断某个数组是否为空,即
ind===arr.length,如果条件成立,则结果值肯定在另一个数组中,返回另一个数组对应下标元素即可 - 如果
k===1,则说明只需要找到第一大的元素即可,此时返回两个下标位置元素的最小值即可 - 尝试将数组1下标向后调整
k>>1位,如果数组长度不够走k>>1步,则调整到数组末尾 - 根据数组1走的步数调整数组2的步数
k-step1,如果数组长度不够走k-step1步,则调整到数组末尾 - 因为存在数组2的长度不够数组1剩余步数的情况,所以求得数组2的步数后需要倒推一下数组1的步数
- 如果调整后数组1位置的值小于数组2位置的值,则说明目标值肯定不在
arr1[ind1+step1]之前的元素中,因为它们是当前两数组区间中前step1小的元素的一部分,更新数组1的起始下标,反之更新数组2的起始下标
假设函数传入参数依次为 arr1,ind1,arr2,ind2,k
该题解第5步适用于 getNum([1],0,[3,4,5,6,7,8],0,4) 的情况
第6、7步适用于 getNum([3,4,5,6,7,8],0,[1],0,4) 的情况
代码如下:
var findMedianSortedArrays = function (nums1, nums2) {
// 两数组长度之和
const len = nums1.length+nums2.length,
// 中位数为第 mid 大的值
// 如果len为奇数,则中位数就是第 mid 大的值
// 否则是第 mid 和第 mid+1 大的值
mid = (len+1)>>1;
// 奇数
if(len%2) return getNum(nums1,0,nums2,0,mid)
// 偶数
return (getNum(nums1,0,nums2,0,mid)+getNum(nums1,0,nums2,0,mid+1))/2
/**
@param {array} arr1 被寻找的第一个数组
@param {number} ind1 第一个数组的起始下标
@param {array} arr2 被寻找的第二个数组
@param {number} ind2 第二个数组的起始下标
@param {number} k 要寻找的是第k大值
@return {number} 找到的第k大值
*/
function getNum(arr1,ind1,arr2,ind2,k){
// 某个数组空了,剩下的个数取另一个没空的数组的值
if(ind1 === arr1.length) return arr2[ind2+k-1]
if(ind2 === arr2.length) return arr1[ind1+k-1]
// 还差一步,返回当前两个值中的最小值
if(k==1) return Math.min(arr1[ind1],arr2[ind2])
// 第一个数组尝试向后走一半距离,如果不够,走到数组末尾
let step1 = Math.min(k>>1,arr1.length-ind1)
// 第二个数组尝试向后走k-第一个数组走的距离,如果不够,走到数组末尾
let step2 = Math.min(k-step1,arr2.length-ind2)
// 再用step2倒推一下step1
step1 = k-step2
// 说明 arr1[ind1+step1] 之前的元素可以排除
if(arr1[ind1+step1-1]<arr2[ind2+step2-1]){
return getNum(arr1,ind1+step1,arr2,ind2,k-step1)
}
// 反之 arr2[ind2+step2] 之前的元素可以排除
return getNum(arr1,ind1,arr2,ind2+step2,k-step2)
}
};
其实因为本题的数据量并不大,所以以上三种方法多次提交都可以达到耗时击败 90+% 用户的程度,但是从时间复杂度来讲,只有最后一种达到了本题要求的 O(log (m+n))
至此我们就完成了 leetcode-4-寻找两个正序数组的中位数
如有任何问题或建议,欢迎留言讨论!