原题链接:寻找两个正序数组的中位数
01.合并
时间复杂度: O ( m + n ) O(m + n) O(m+n), 空间复杂度: O ( m + n ) O(m + n) O(m+n)
将nums1
和nums2
合并到merge
中,直接输出中位数
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size(), m = nums2.size();
vector<int> merge(m + n);
int i = 0, j = 0, idx = 0;
while(i < n && j < m)
{
if(nums1[i] < nums2[j])
merge[idx ++] = nums1[i ++];
else
merge[idx ++] = nums2[j ++];
}
while(i < n) merge[idx ++] = nums1[i ++];
while(j < m) merge[idx ++] = nums2[j ++];
int c = (idx + 1) >> 1;
if(idx & 1) return (double)merge[c - 1];
return (merge[c] + merge[c - 1]) / 2.0;
}
};
02.指针
时间复杂度: O ( m + n ) O(m + n) O(m+n), 空间复杂度: O ( 1 ) O(1) O(1)
通过指针l1、r1、l2、r2
来指向nums1
和nums2
的最大、最小值,依次删除整体的最大和最小值,重复(m + n - 1) / 2
次,留下的即是中位数。当l1 > r1
时,则中位数在num2
中;当l2 > r2
时,则中位数在nums1
中;否则中位数即是nums1
和nums2
中各有一个。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size(), m = nums2.size();
if(m + n == 1)
{
if(m) return (double)nums2[0];
else return (double)(nums1[0]);
}
if(m == 1 && n == 1)
return (nums1[0] + nums2[0]) / 2.0;
int l1 = 0, l2 = 0, r1 = n - 1, r2 = m - 1;
int c = (m + n - 1) >> 1;
for(int i = 0; i < c; ++ i)
{
if(l1 >= n) ++ l2;
else if(l2 >= m) ++ l1;
else
{
if(nums1[l1] < nums2[l2]) ++ l1;
else ++ l2;
}
if(r1 < 0) -- r2;
else if(r2 < 0) -- r1;
else
{
if(nums1[r1] > nums2[r2]) -- r1;
else -- r2;
}
}
if(l1 > r1) return (nums2[l2] + nums2[r2]) / 2.0;
else if(l2 > r2) return (nums1[l1] + nums1[r1]) / 2.0;
return (nums1[l1] + nums2[l2]) / 2.0;
}
};
03.划分数组
时间复杂度: O ( l o g ( m i n ( m , n ) ) ) O(log(min(m, n))) O(log(min(m,n))), 空间复杂度: O ( 1 ) O(1) O(1)
首先,我们确保第一个数组 nums1
的长度小于或等于第二个数组 nums2
的长度。如果不满足这个条件,可以交换两个数组以确保满足。
接下来,我们使用二分查找的方法在第一个数组 nums1
中选择一个分割点 pos1
,该分割点将数组划分为左右两个部分。同样,在第二个数组 nums2
中选择一个分割点 pos2
,也将数组划分为左右两个部分。分割点的位置可以通过以下公式计算得到:
pos1= (l + r) / 2; pos2 = (n + m + 1) / 2 - pos1
其中,low
和 high
分别代表第一个数组的起始和结束位置,n
和 m
分别代表两个数组的长度。
根据分割点的位置,我们可以计算出左侧部分的最大值和右侧部分的最小值。假设 maxLeft1
、minRight1
分别表示第一个数组左侧部分的最大值和右侧部分的最小值,maxLeft2
、minRight2
分别表示第二个数组左侧部分的最大值和右侧部分的最小值。
接下来,我们比较 maxLeft1
和 minRight2
以及 maxLeft2
和 minRight1
的大小关系。如果满足 maxLeft1 <= minRight2
和 maxLeft2 <= minRight1
,那么说明分割点的位置划分得合理,左侧部分的元素都小于等于右侧部分的元素。这时,根据数组长度之和的奇偶性,我们可以得到中位数的值。
- 如果数组长度之和
(n + m)
是偶数,那么中位数是左侧部分的最大值max(maxLeft1, maxLeft2)
和右侧部分的最小值min(minRight1, minRight2)
的平均值。 - 如果数组长度之和
(n + m)
是奇数,那么中位数是左侧部分的最大值max(maxLeft1, maxLeft2)
。
如果上述条件不满足,我们需要调整分割点的位置。
- 如果
maxLeft1 > minRight2
,说明第一个数组的左侧部分太大,我们需要将第一个数组的分割点向左移动,即r = pos1 - 1
。 - 如果
maxLeft2 > minRight1
,说明第二个数组的左侧部分太大,我们需要将第一个数组的分割点向右移动,即l = pos1 + 1
。
通过不断调整分割点的位置,我们最终可以找到合适的分割点,使得两个分割点左侧部分的元素个数与右侧部分的元素个数相等(或差距不超过1),从而得到中位数。
总的来说,这个算法通过二分查找的思想,将问题的规模不断缩小一半,因此时间复杂度为 O ( l o g ( m i n ( n , m ) ) ) O(log(min(n, m))) O(log(min(n,m))),其中 n 和 m 分别是两个数组的长度。这种算法可以在对数时间复杂度内寻找两个已排序数组的中位数。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
if (n > m) {
return findMedianSortedArrays(nums2, nums1);
}
int l = 0;
int r = n;
while (l <= r) {
int pos1 = (l + r) / 2;
int pos2 = (n + m + 1) / 2 - pos1;
int maxLeft1 = (pos1 == 0) ? INT_MIN : nums1[pos1 - 1];
int minRight1 = (pos1 == n) ? INT_MAX : nums1[pos1];
int maxLeft2 = (pos2 == 0) ? INT_MIN : nums2[pos2 - 1];
int minRight2 = (pos2 == m) ? INT_MAX : nums2[pos2];
if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
if ((n + m) % 2 == 0) {
return (max(maxLeft1, maxLeft2) + min(minRight1, minRight2)) / 2.0;
} else {
return max(maxLeft1, maxLeft2);
}
} else if (maxLeft1 > minRight2) {
r = pos1 - 1;
} else {
l = pos1 + 1;
}
}
return -1;
}
};