给定两个大小分别为 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)
思路:
注意特殊情况(递归终止条件):
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,这样就能保证如果有数组空了,一定是 len1
if (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)