给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-10^6 <= nums1[i], nums2[i] <= 10^6
如果不考虑时间复杂度的要求,这道题可以用暴力去解决,根据中位数的定义,中位数一定是有序序列的中间一位或者两位的平均值,那么我只要同时开始遍历两个数组,找到中间的两个数,然后根据两个数组长度总和的奇偶,奇数的话就取小值,偶数就取两者的平均数。
暴力的解法固然可以AC,但是还是不满足题目中对于时间复杂度的要求,而且对于这种在有序的数组中找数的题目,第一反应都是用二分去做,只是这题比较特殊,是要在两个数组里面求合并后的值,当然我们可以合并数组后再二分查找,但事实上,既然暴力解法我们都不需要真正的合并数组,那使用二分查找的时候肯定也可以不用。
于是我们再看下中位数的定义:
中位数(Median)又称中值,统计学中的专有名词,是按顺序排列的一组数据中居于中间位置的数,代表一个样本、种群或概率分布中的一个数值,其可将数值集合划分为相等的上下两部分。
既然中位数可以将数值集合划分为相等的两部分,那么我们先在一个数组里面看中位数,将数组从中间分割开来,中位数一定在坐标i和i-1两个数中,如果是数组长度为奇数那么取min(nums[i-1], nums[i]),而如果是偶数则为 (nums[i-1]+nums[i])/2。
1 3 5 | 8(i) 9 2 4 6 | 7(i) 10 12
同样如果是两个数组,将两个数组从中间分隔开,使得左边的数据个数=右边的数据个数或者左边比右边多一个,那这个时候基本上就能确定中位数的位置了
1 3 5 | 8(i) 9 2 4 6 | 7(j) 10 12
当然仅仅只是数量上满足条件当然还不够,如果中位数就在分割线附近的话,那么分割线附近的4个数,在合并两个数组后应该是一个连续字串,也就是需要满足
nums1[i-1]<=nums2[j] && nums1[j-1]<=nums2[i]
这个条件,于是我们二分查找的条件也就出来了,但是还是需要注意如下四种边界情况。
1 2 3(i) | | 4(j) 5 6 7
| 4(i) 5 6 1 2 3 4(j) |
1 2 3(i) | | 4(j) 5 6
| 4(i) 5 6 1 2 3(j) |
class Solution { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int n = nums1.length; int m = nums2.length; boolean flag = (n+m)%2==0; int mid = (n+m)/2+1; int i = 0, j = 0; int a = 0, b = 0; int cnt = 1; while( i<n || j < m ) { a = b; if( i!=n ) { if( j!=m ) { if( nums1[i] < nums2[j] ) { b = nums1[i]; i ++; } else { b = nums2[j]; j ++; } } else { b = nums1[i]; i ++; } } else { b = nums2[j]; j ++; } if( cnt == mid ) { break; } cnt ++; } if( flag ) { return (a+b)/2.0; } return b; } }
class Solution { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int n = nums1.length; int m = nums2.length; if( n > m ) { return findMedianSortedArrays(nums2, nums1); } int left = 0; int rigth = n; int i = 0; int j = 0; // 定义的分割线需要满足 nums1[i-1]<=nums2[j] && nums1[j-1]<=nums2[i] // 1 3 5 | 8(i) 9 // 2 4 6 | 7(j) 10 12 // 左边数字个数应该为 (n+m+1)/2 = i + j while( left<rigth ) { i = (left + rigth +1 ) / 2; j = (n+m+1)/2-i; // 二分确定能满足条件的分割线 if( nums1[i-1]>nums2[j] ) { rigth = i - 1; } else { left = i; } } i = left; j = (n+m+1)/2-i; // 边界处理 int numsI1 = i==0? Integer.MIN_VALUE: nums1[i-1]; int numsI = i==n? Integer.MAX_VALUE: nums1[i]; int numsJ1 = j==0? Integer.MIN_VALUE: nums2[j-1]; int numsJ = j==m? Integer.MAX_VALUE: nums2[j]; if( (n+m)%2 == 1 ) { return Math.max(numsI1, numsJ1); } else { return (Math.max(numsI1, numsJ1) + Math.min(numsI, numsJ)) * 0.5; } } }