题目描述
给定两个大小分别为 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
解法
- 遍历解法
思路:
- 先找到中位数在哪
- 依次遍历两个数组,依次从两个数组前面取元素(都是数组最小的元素),比较大小,取较小的,
- 更新元素小的那个数组的索引,向后移动一位,继续遍历比较
- 只需要遍历到中位数次数即可。
java代码:
class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {// 先找到计算中位数的位置:如果是奇数则去中间的,否则取中间两个的平均数ArrayList<Integer> arr = new ArrayList<>();if ((nums1.length + nums2.length) % 2 == 1) {arr.add((nums1.length + nums2.length) / 2 + 1);} else {arr.add((nums1.length + nums2.length) / 2);arr.add((nums1.length + nums2.length) / 2 + 1);}// 中位数的和int sum = 0;int index1 = 0; // 数组1的索引int index2 = 0; // 数组2的索引int count = 0; // 已经排序的总数// 遍历两个数组,依次从两个数组取元素,比较大小,取较小的while (count < (nums1.length + nums2.length) / 2 + 1) {// 比较两个数组的元素,更新元素小的那个数组的索引,并取出小的元素int min;if (index1 >= nums1.length) {// 说明nums1数组被取完了,去取nums2数组min = nums2[index2];index2 ++;} else if (index2 >= nums2.length) {// 说明nums2数组被取完了,去取nums1数组min = nums1[index1];index1 ++;} else if (nums1[index1] < nums2[index2]) {// nums1数组的元素更小min = nums1[index1];index1 ++;} else {// nums2数组的元素更小min = nums2[index2];index2 ++;}count ++;if (arr.contains(count)) {// 计算平均数的和sum += min;}}return (double)sum / arr.size();}
}
复杂度
时间复杂度:O(m+n)
,m和n分别为数组长度
空间复杂度:O(1)
- 二分 + 递归
思路:
- 找第k大的数,那么每次先去排除k/2个数;
- 分别两个数组里找第k/2的数,如果 k 是奇数,向下取整,然后比较两个数组的第k/2的数大小,小的那个数组的第k/2的数前面的数,肯定不是第K大的数;
- 把小的数组的前面的数排除掉(k/2个),因为前面已经排除了k/2个数,所有后面只在两个数组里找第 k- k/2 大的数即可
注意特殊情况(递归终止条件):
- 每次都是取 k/2 的数进行比较,有时候可能会遇到数组长度小于 k/2的时候,那么只要取末尾那个数就行
- 如果有数组为空,则只取不为空的数组中位数
java代码:
class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int len1 = nums1.length;int len2 = nums2.length;int sumLen = len1 + len2;if (sumLen % 2 == 1) { // 奇数个return getKth(nums1, nums2, 0, len1 - 1, 0, len2 - 1, sumLen / 2 + 1);} else { // 偶数个int mid1 = getKth(nums1, nums2, 0, len1 - 1, 0, len2 - 1, sumLen / 2);int mid2 = getKth(nums1, nums2, 0, len1 - 1, 0, len2 - 1, sumLen / 2 + 1);return (mid1 + mid2) / 2.0d;}// 时间复杂度是O(log(k)),因为k = (m + n)/2 ,所以时间复杂度O(log(m + n))}/*** 辅助函数:找两个数组第k大的数* 二分 + 递归* 整体思路:找第k大的数,那么每次先去排除k/2个数* 分别两个数组里找第k/2的数,如果 k 是奇数,向下取整,然后比较两个数组的第k/2的数大小,小的那个数组的第k/2的数前面的数,肯定不是第K大的数* 把小的数组的前面的数排除掉(k/2个),因为前面已经排除了k/2个数,所有后面只在两个数组里找第 k- k/2 大的数即可* 特殊情况:1. 每次都是取 k/2 的数进行比较,有时候可能会遇到数组长度小于 k/2的时候,那么只要取末尾那个数就行* 2.如果有数组为空,则只取不为空的数组中位数** @param nums1 数组1* @param nums2 数组2* @param start1 数组1的起始索引* @param end1 数组1的结束索引* @param start2 数组2的起始索引* @param end2 数组2的结束索引* @param k* @return*/private static int getKth(int[] nums1, int[] nums2, int start1, int end1, int start2, int end2, int k) {int len1 = end1 - start1 + 1;int len2 = end2 - start2 + 1;// 让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1if (len1 > len2) {return getKth(nums2, nums1, start2, end2, start1, end1, k);}// 下面是递归的两个结束条件// 1. 如果第一个数组长度为0,直接找第二个数组中位数即可if (len1 == 0) {return nums2[start2 + k -1];}// 2. 如果k等于1,直接找两个数组中第一个元素小的那个元素if (k == 1) {return Math.min(nums1[start1], nums2[start2]);}// 找两个个数组的第k/2个索引// 小心这里可能数组越界,即第一个数组的长度没有第k/2个数大了,那么就取最后一个int index1 = start1 + Math.min(len1, k/2) -1;int index2 = start2 + Math.min(len2, k/2) -1;// 比较两个数组索引位置的元素大小,小的那个前面的元素不要了,继续递归调用找第k- k/2 大的数// 注意点:这里的第k- k/2 大的,需要考虑到上面可能第一个数组的长度没有第k/2个数大了// 那么就只排除了第一个数组长度的数,所以,不应该找第k- k/2 大的数,而是找第k- num1.length 大的数,// 两种情况综合考虑,即找第 k - Math.min(len2, k/2)大的数,可用k - (index1 - start1 + 1)表示if (nums1[index1] < nums2[index2]) {return getKth(nums1, nums2, index1 + 1, end1, start2, end2, k - (index1 - start1 + 1));} else {return getKth(nums1, nums2, start1, end1, index2 + 1, end2, k - (index2 - start2 + 1));}}
}
复杂度
时间复杂度:O(log(m + n))
,时间复杂度是O(log(k))
,因为k = (m + n)/2
,所以时间复杂度O(log(m + n))
空间复杂度:O(1)