题目描述
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
算法思路
首先,观察题目,题目要求时间复杂度为O(log (m+n)),要实现log级的复杂度的算法一般会想到二分的思想,每次去掉接近一半的元素。
再回顾一下中位数的定义,如果某个有序数组长度是奇数,中位数取排在最中间的数;如果是偶数,中位数取最中间的两个数字的平均值。因此问题可以转化为求两个有序数组中第K大的数的问题。题中涉及两个有序数组,假设两个有序数组的长度分别为m和n,由于两个数组长度之和 m+n 的奇偶不确定,因此需要分情况来讨论。若(m+n)为奇数,直接找到最中间的数即可;若为偶数则需要求最中间两个数的平均值。
为了简化代码,不分情况讨论,使用一个小trick,分别找排在第 (m+n+1) / 2 个和 (m+n+2) / 2 个的数字,然后求其平均值即可,这对奇偶数均适用。当(m+n)为奇数时, (m+n+1) / 2 与 (m+n+2) / 2 的值相等,两者相加除以2仍然是其本身。
为了避免产生新的数组从而增加时间复杂度,我们使用两个变量i和j分别来标记数组nums1和nums2的起始位置。
考虑一些边界条件:
1)当某一个数组nums1的起始位置大于等于其数组长度时,说明其所有数字均已经被淘汰了,相当于一个空数组,那么实际上就变成了在另一个数组nums2中找第K大的数字,直接取nums2[j+k-1]即可。
2)当K=1时,取最小的数字即可,只需比较nums1和nums2的起始位置i和j上的数字即可。
下面重点来看如何实现找到第K个元素:
因为我们需要在两个有序数组中找到第K个元素,为了加快搜索的速度,我们要使用二分法,对K二分,意思是我们需要分别在nums1和nums2中查找第K/2个元素,注意这里由于两个数组的长度不定,所以有可能某个数组没有第K/2个数字,所以我们需要先检查一下,数组中到底存不存在第K/2个数字,如果存在就取出来,否则就赋值一个整型最大值INT_MAX。如果某个数组没有第K/2个数字,那么我们就淘汰另一个数字的前K/2个数字即可。有没有可能两个数组都不存在第K/2个数字呢,这道题里是不可能的,因为我们的K不是任意给的,而是给的m+n的中间值,所以必定至少会有一个数组是存在第K/2个数字的。最后就是二分法的核心啦,比较这两个数组的第K/2小的数字midVal1和midVal2的大小,如果第一个数组的第K/2个数字小的话,那么说明我们要找的数字肯定不在nums1中的前K/2个数字,所以我们可以将其淘汰,将nums1的起始位置向后移动K/2个,并且此时的K也自减去K/2,调用递归。反之,我们淘汰nums2中的前K/2个数字,并将nums2的起始位置向后移动K/2个,并且此时的K也自减去K/2,调用递归即可。
实现代码
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size();
int n = nums2.size();
int left = (m + n + 1) / 2;
int right = (m + n + 2) / 2;
return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
}
int findKth(vector<int>& nums1, int i, vector<int>& nums2, int j, int k){
if(i >= nums1.size()) return nums2[j + k - 1];
if(j >= nums2.size()) return nums1[i + k - 1];
if(k == 1){
return min(nums1[i], nums2[j]);
}
int mid1 = ((i + k / 2 - 1) < nums1.size()) ? nums1[i + k / 2 - 1] : INT_MAX;
int mid2 = ((j + k / 2 - 1) < nums2.size()) ? nums2[j + k / 2 - 1] : INT_MAX;
if(mid1 < mid2){
return findKth(nums1, i + k / 2, nums2, j , k - k / 2);
}else{
return findKth(nums1, i, nums2, j + k / 2 , k - k / 2);
}
}
};
这道题的思路参照了leetcode题解思路,虽然自己想到了找第K大的数,但是没有很快想出如何处理数组长度小于k/2的情况,算法执行速度也较慢,还没想到优化策略。
参考链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/comments/