寻找两个正序数组的中位数
给定两个大小为 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
示例 3:
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例 4:
输入:nums1 = [], nums2 = [1]
输出:1.00000
示例 5:
输入:nums1 = [2], nums2 = []
输出:2.00000
解题思路
我们可以将该题理解为求两个有序数组中第 K 小的元素,可使用优先队列(小根堆)来解决。
先将两个数组中的元素加入到优先队列中。
- 若队列的长度(len)为偶数:则将中间位置(len / 2 和 len / 2 - 1)的两个元素记录下来并计算中位数。
- 若队列的长度(len)为奇数:则将中间位置(len / 2)的元素记录下来,即为中位数。
在将元素出队时,只需要前len / 2个元素出队即可,后面的元素不需要进行操作。
代码
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int sum = m + n;
PriorityQueue<Integer> pqNums = new PriorityQueue<>();
for (int i=0;i<m;i++) {
pqNums.offer(nums1[i]);
}
for (int i=0;i<n;i++) {
pqNums.offer(nums2[i]);
}
return getMid(pqNums);
}
//求中位数
private double getMid(PriorityQueue<Integer> pqNums) {
int flag = 0;
double a = 0.0;
double b = 0.0;
int len = pqNums.size();
if (pqNums.size() % 2 == 0) {
while (flag <= len / 2) {
if (flag == (len / 2) - 1) {
a = pqNums.poll();
} else if (flag == len / 2) {
b = pqNums.poll();
} else {
pqNums.poll();
}
flag++;
}
return a / 2.0 + b / 2.0;
} else {
while (flag <= len / 2) {
a = pqNums.poll();
flag++;
}
return a * 1.0;
}
}
}
空间复杂度:定义了一个优先队列,长度为 m + n,所以复杂度为O(m + n)
优化(使用归并排序来进行数组的合并)
在进行归并排序将两个数组合并时,不用将两个数组全部遍历完。当 sum 为奇数:只需要遍历到 Merge 数组中元素的个数为 sum / 2 + 1 即可,然后返回此时 Merge 数组中最后一个元素即为中位数;当 sum 为偶数:要分别遍历到数组中元素个数为 sum / 2 + 1 和 sum / 2 时,分别取两种情况下的数组的最后一个元素,然后进行中位数计算。
分而治之(divide - conquer):有三个步骤
- 分解: 把待排序的 n 个元素的序列分解成两个子序列, 每个子序列包括 n/2 个元素。
- 治理: 对每个子序列分别调用归并排序MergeSort, 进行递归操作。
- 合并: 合并两个排好序的子序列,生成排序结果。
归并排序是一种稳定的排序,可用顺序存储结构。
代码
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int sum = nums1.length + nums2.length;
if (sum % 2 != 0) {
return getMid(nums1, nums2, sum / 2) * 1.0;
} else {
return getMid(nums1, nums2, sum / 2 - 1) / 2.0
+ getMid(nums1, nums2, sum / 2) / 2.0;
}
}
//归并排序(其中k为两个数组合并到的长度,再往后的元素就不需要合并进来了(节省时间))
private double getMid(int[] nums1, int[] nums2, int k) {
int[] Merge = new int[nums1.length + nums2.length];
int i = 0, j = 0, m = 0;
while(i < nums1.length&&j < nums2.length&&m <= k) {
if (nums1[i] < nums2[j]) {
Merge[m++] = nums1[i++];
} else {
Merge[m++] = nums2[j++];
}
}
while (i < nums1.length&&m <= k) { //若nums1有剩余
Merge[m++] = nums1[i++];
}
while (j < nums2.length&&m <= k) {
Merge[m++] = nums2[j++];
}
return Merge[m - 1];
}
}
空间复杂度:需要一个辅助向量来暂存两有序子文件归并的结果,故其辅助空间复杂度为O(m + n)
时间复杂度:O((m + n) / 2) = O(n + m)