「这是我参与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 == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= 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-寻找两个正序数组的中位数
如有任何问题或建议,欢迎留言讨论!