一、题目描述
来源:力扣(LeetCode)
给定两个大小分别为 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 == m
- nums2.length == n
- 0 <= m <= 1000
- 0 <= n <= 1000
- 1 <= m + n <= 2000
- -106 <= nums1[i], nums2[i] <= 106
二、思考分析
1 简单暴力-合并数组求解中位数
- 直接合并两个有序数组,得到一个数组。然后直接得到中位数即可。这个可能是我们第一时间想到的方法。不考虑时间复杂度的话。
- 这种方法的时间复杂度为
网络异常,图片无法展示|网络异常,图片无法展示|
2. 二分法
- 对于一个正序的数组,查找数组的中位数,实质上就是寻找数组中第X小的数组(X就是中位数位于数组中的第几位),所以我们可以把这个问题转换为寻找第X个数。
- 对于两个正序数组来说,就是寻找两个数组中第 2/X 小的数组
- 然后比较两个数组的第2/X个数的大小
- 如果 第一个数组num1 的第2/X 个数字小于于第二个数字的 那么说明 num1的前 2/X 个数组中不存在我们最终需要找的整体第X/2小的值 ,可以排除这些数
- 因为排除的数组肯定位于中位数前面 然后在排除之外的两个新数组 X-排除的数 小的即可
- 为了防止数组长度小于 X/2,所以每次比较 min(X/2,len(数组) 对应的数字,把小的那个对应的数组的数字排除,将两个新数组进入递归,并且 X 要减去排除的数字的个数。递归出口就是当 X=1 或者其中一个数字长度是 0 了。
三、代码实现
class Solution { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int n = nums1.length; int m = nums2.length; int x = n + m; if(x % 2==0){ int l = findnum(nums1, 0, n -1, nums2, 0, m -1, x/2 ); int r = findnum(nums1, 0, n -1, nums2, 0, m -1, x/2+1 ); return (l + r) / 2.0 ; } return findnum(nums1, 0, n -1, nums2, 0, m -1, x/2+1); } private int findnum(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int x) { int len1 = end1 - start1 +1; int len2 = end2 - start2 +1; //为了好区别遍历 我们统一将num1当做短的数组 if (len1 > len2) return findnum(nums2, start2, end2, nums1, start1, end1, x); if (len1 ==0) return nums2[start2 + x -1]; if (x ==1) return Math.min(nums1[start1], nums2[start2]); int i = start1 + Math.min(len1, x / 2) -1; int j = start2 + Math.min(len2, x / 2) -1; if (nums1[i] > nums2[j]) { return findnum(nums1, start1, end1, nums2, j +1, end2, x - (j - start2 +1)); } else { return findnum(nums1, i +1, end1, nums2, start2, end2, x - (i - start1 +1)); } } }
时间复杂度 O(log(m+n))
总结
多思考问题的本质,然后将问题转为我们我们熟悉的思想。