题目描述
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
问题评价
该题技巧性较强,需要从设计上实现二分法来达到目的复杂度;
边界处理仍然是问题的难点;
解题思路
寻找中位数,最终定位为寻找第K位的值。k是根据总体数据量的奇、偶性计算得到的。
1 、假设nums1.length = m, nums2.length = n; m < n。
2、若(m + n) % 2 == 0
, 表示两数组之和为偶数,应该是有两个中位数,需要求均值。如果和为奇数,则只有一位,在代码设计最初考虑该问题。
3、为了使得方法的统一,在最初时,对数组进行处理,统一使得传进方法的短数组为nums1,这样在后续进行k/2与。
4、如果len1-start1 == 0
,则表示nums1已经全部加入前k个了,则第k个为nums2[k -1]; 在方法findKth()中的k是一直变化的,初始时,k为两个数组中排序之后的第k个数的位置;k在方法中的真正含义为“还需要找到多少个数才能达到k个”;因此假设nums1.length ==0;
,此时len1-start1 == 0
, 则中位数就是nums2[k - 1],即在nums1中找到了0个数,还需要找k个数,第k个数就是nums[k - 1];
其中:len1为数组nums总长度;start1为其计算中数据的起始坐标;len2与start2同理;查看代码后可更好的理解。
5、 如果k == 1,则表示前k-1小的数已经找过了,则第k个数肯定是nums1[start1]
和nums2[start2]
中较小的那个数。
6、 下面接着就是常规的情况:即nums1中包含一部分k,nums2中也包含一部分的k,因此就从每个数组的k/2那里开始比较(也相当于每次都会有一半的数被加入前k个,因此时间复杂度为O(log(m + n)))
。
采用p1和p2分别记录当前nums1和nums2需要比较的那个位,由于nums1比较短,因此有可能k/2的位置已经超出了nums1的长度,因此nums1还需要做特殊处理,即求解p1处所示;由于p1做了特殊处理,那p2也就要做特殊处理。总之,start1~p1
和start2~p2
的和一定为k。
1)若nums1[p1 - 1] < nums[p2 - 1]
,则表明[start1, p1)之间的值在前k个数中;
2)若nums[p1 - 1] > nums2[p2- 1]
,则表明[start2, p2)之间的值在前k个数中;
3)若两值相等,则表明[start1, p1)+[start2, p2)的个数为k,则结果直接返回其中一个即可。
边界问题
1、为什么比较的p1和p2的前一个位的数,而不是p1和p2位置的数呢?这就是边界问题。
举例说明:假设start1== start2 == 0
, 则p1 = Math.min(len1, k / 2)
; p2 = k - p1
,即p1 + p2 == k
;假设p1 = 5, p2 = 7; 则k = 12; 在数组中nums[5]
其实是第6个数,nums[7]
其实是第8个数,所以我们比较的是nums1[p1 - 1]
与nums2[p2 - 1]
的值。
2、注意每个数组的start位置,其实是有效数据的前一位,因此可以直接做差来求数据的个数。例如[1,3,4,5],记录为start=0,length=4。在后续进行推进修改时,start仍旧为有效数据位的前一位。
代码
public class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len1 = nums1.length;
int len2 = nums2.length;
int size = len1 + len2;
if(size % 2 == 1)
return findKth(nums1, 0, len1, nums2, 0, len2, size / 2 + 1);
else
return (findKth(nums1, 0, len1, nums2, 0, len2, size / 2)
+ findKth(nums1, 0, len1, nums2, 0, len2, size / 2 + 1)) /2;
}
public double findKth(int[] nums1, int start1, int len1, int[] nums2, int start2, int len2, int k)
{
if(len1 - start1 > len2 -start2) // 传进来的时候统一让短的数组为nums1
return findKth(nums2, start2, len2, nums1, start1, len1, k);
if(len1 - start1 == 0) // 表示nums1已经全部加入前K个了,第k个为nums2[k - 1];
return nums2[k - 1];
if(k == 1)
return Math.min(nums1[start1], nums2[start2]);
// k==1表示已经找到第k-1小的数,下一个数为两个数组start开始的最小值
int p1 = start1 + Math.min(len1 - start1, k / 2); // p1和p2记录当前需要比较的那个位
int p2 = start2 + k - p1 + start1;
if(nums1[p1 - 1] < nums2[p2 - 1])
return findKth(nums1, p1, len1, nums2, start2, len2, k - p1 + start1);
else if(nums1[p1 - 1] > nums2[p2 -1])
return findKth(nums1, start1, len1, nums2, p2, len2, k - p2 + start2);
else
return nums1[p1 - 1];
}
}
参考:
Leetcode4—>求两个排序数组的中位数