题目描述
给定两个大小分别为 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
为了在时间复杂度为 O(log(m+n)) 的情况下找到两个正序数组的中位数,可以使用一种叫作"中位数的性质"(Median of Medians)的算法。这个算法的基本思想是通过不断缩小搜索范围来找到中位数。下面代码实现了一个时间复杂度为 O(log(min(m, n))) 的算法,通过二分查找的方式在较短的数组上执行,以达到 O(log(m+n)) 的整体时间复杂度。
递归函数的算法原理是基于分治策略的。它用于在两个已排序数组 nums1
和 nums2
中寻找第 k
个元素。
算法步骤如下:
- 首先,函数会检查两个数组的长度,如果其中一个数组的长度小于等于
k
,那么函数就会直接返回另一个数组的第k
个元素。 - 如果两个数组的长度都大于
k
,那么函数会取k
的一半,即p = k / 2
。这里用到了二分查找的思想,即将问题规模缩小一半。 - 然后,函数会在两个数组中寻找第
p
个元素,记为x
和y
。 - 如果
x
小于y
,那么说明第k
个元素一定在nums1
的后半段,因此在nums1
的后半段和nums2
中寻找第k - p
个元素。 - 如果
x
大于等于y
,那么说明第k
个元素一定在nums2
的后半段,因此在nums1
的前半段和nums2
的后半段中寻找第k - p
个元素。 - 重复以上步骤,直到找到第
k
个元素,或者剩余的元素个数小于等于p
。
这个函数的作用是在两个已排序数组中寻找第 k
个元素,它利用了分治策略和二分查找的思想,可以高效地处理大规模数据。
这个代码实现了一个寻找两个有序数组的中位数的算法。为了方便解释,我们先定义以下的基本概念:
nums1
和nums2
是两个有序数组,它们的长度分别为m
和n
。- 中位数:对于一个有序数组,中位数是指位于数组中间位置的数。当数组的长度为奇数时,中位数就是位于正中间的数;当数组的长度为偶数时,中位数是位于中间两个数的平均值。
该算法的核心思路是利用二分法进行查找。具体来说,这个函数通过比较两个数组的相应元素来决定下一步查找的方向。函数 f(i, j, k)
用来查找由 nums1
的前 i
个元素和 nums2
的前 j
个元素组成的长度为 k
的子序列的中位数。这里的 i
、j
和 k
都是整数。
函数的返回值是这个子序列的中位数。其中,p
是 k
的一半,x
和 y
分别是 nums1
和 nums2
在对应位置上的元素(如果该位置越界,则认为对应的元素为无穷大)。然后根据 x
和 y
的大小关系来递归查找子序列的中位数。
最后,该算法分别以 (m + n + 1) // 2
和 (m + n + 2) // 2
为长度调用函数 f
来找到两个中位数,然后取这两个中位数的平均值作为最终结果。这样做是为了处理 m + n
为奇数和偶数两种情况。
这是一个具体的例子来演示这个函数是如何工作的:
假设我们有两个有序数组 nums1 = [1, 3, 5]
和 nums2 = [2, 4, 6]
。首先,我们会把这两个数组合并成一个有序数组 [1, 2, 3, 4, 5, 6]
,然后取中间的两个数 3
和 4
的平均值,即 3.5
,作为两个有序数组的中位数。
C++
#include <functional>
#include <vector>
#include <iostream>
using namespace std;
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size();
// 定义一个递归函数f,用于查找两个数组中第k小的数
function<int(int, int, int)> f = [&](int i, int j, int k) {
if (i >= m) {
return nums2[j + k - 1]; // nums1中已经没有元素,直接返回nums2中第k小的数
}
if (j >= n) {
return nums1[i + k - 1]; // nums2中已经没有元素,直接返回nums1中第k小的数
}
if (k == 1) {
return min(nums1[i], nums2[j]); // 如果k为1,直接返回两个数组当前位置的较小值
}
int p = k / 2; // 将k分为两部分
// 分别取两个数组中第i+p-1和j+p-1位置的数,若越界则取一个较大的数
int x = i + p - 1 < m ? nums1[i + p - 1] : 1 << 30;
int y = j + p - 1 < n ? nums2[j + p - 1] : 1 << 30;
// 根据x和y的大小关系递归调用f函数
return x < y ? f(i + p, j, k - p) : f(i, j + p, k - p);
};
// 调用f函数分别找到第(m+n+1)/2和(m+n+2)/2小的数
int a = f(0, 0, (m + n + 1) / 2);
int b = f(0, 0, (m + n + 2) / 2);
return (a + b) / 2.0; // 返回中位数
}
};
int main()
{
vector<int> nums1 = { 1, 2 };
vector<int> nums2 = { 3, 4 };
Solution sol;
double median = sol.findMedianSortedArrays(nums1, nums2);
cout << "Median: " << median << endl;
}
C++ 另一个表达
#include <vector>
#include <iostream>
#include <climits> // 包含用于INT_MAX的定义
using namespace std;
class Solution {
private:
// 在两个已排序数组中寻找第k个元素的函数
int findKthElement(std::vector<int>& nums1, std::vector<int>& nums2, int i, int j, int k) {
int m = nums1.size(), n = nums2.size();
if (i >= m) {
// 如果数组1已经遍历完成,则直接返回数组2中的第k个元素
return nums2[j + k - 1];
}
if (j >= n) {
// 如果数组2已经遍历完成,则直接返回数组1中的第k个元素
return nums1[i + k - 1];
}
if (k == 1) {
// 当k=1时,返回数组1和数组2当前位置上的较小值
return std::min(nums1[i], nums2[j]);
}
// 取k的一半,分别在nums1和nums2数组中寻找第k/2个元素
int p = k / 2;
int x = i + p - 1 < m ? nums1[i + p - 1] : INT_MAX;
int y = j + p - 1 < n ? nums2[j + p - 1] : INT_MAX;
if (x < y) {
// 如果nums1中对应位置的值较小,则递归在剩余部分中寻找第k-p个元素
return findKthElement(nums1, nums2, i + p, j, k - p);
}
else {
// 如果nums2中对应位置的值较小,则递归在剩余部分中寻找第k-p个元素
return findKthElement(nums1, nums2, i, j + p, k - p);
}
}
public:
// 计算两个已排序数组的中位数的函数
double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2) {
int m = nums1.size(), n = nums2.size();
int a = findKthElement(nums1, nums2, 0, 0, (m + n + 1) / 2);
int b = findKthElement(nums1, nums2, 0, 0, (m + n + 2) / 2);
return (a + b) / 2.0;
}
};
int main()
{
vector<int> nums1 = { 1, 2 };
vector<int> nums2 = { 3, 4 };
Solution sol;
double median = sol.findMedianSortedArrays(nums1, nums2);
cout << "Median: " << median << endl;
}
C#
using System;
public class Solution
{
// 在两个已排序数组中寻找第k个元素的函数
public int FindKthElement(int[] nums1, int[] nums2, int i, int j, int k)
{
int m = nums1.Length, n = nums2.Length;
if (i >= m)
{
// 如果数组1已经遍历完成,则直接返回数组2中的第k个元素
return nums2[j + k - 1];
}
if (j >= n)
{
// 如果数组2已经遍历完成,则直接返回数组1中的第k个元素
return nums1[i + k - 1];
}
if (k == 1)
{
// 当k=1时,返回数组1和数组2当前位置上的较小值
return Math.Min(nums1[i], nums2[j]);
}
// 取k的一半,分别在nums1和nums2数组中寻找第k/2个元素
int p = k / 2;
int x = i + p - 1 < m ? nums1[i + p - 1] : int.MaxValue;
int y = j + p - 1 < n ? nums2[j + p - 1] : int.MaxValue;
if (x < y)
{
// 如果nums1中对应位置的值较小,则递归在剩余部分中寻找第k-p个元素
return FindKthElement(nums1, nums2, i + p, j, k - p);
}
else
{
// 如果nums2中对应位置的值较小,则递归在剩余部分中寻找第k-p个元素
return FindKthElement(nums1, nums2, i, j + p, k - p);
}
}
// 计算两个已排序数组的中位数的函数
public double FindMedian(int[] nums1, int[] nums2)
{
int m = nums1.Length, n = nums2.Length;
int a = FindKthElement(nums1, nums2, 0, 0, (m + n + 1) / 2);
int b = FindKthElement(nums1, nums2, 0, 0, (m + n + 2) / 2);
return (a + b) / 2.0;
}
public static void Main()
{
Solution solution = new Solution();
int[] nums1 = { 1, 3, 5 };
int[] nums2 = { 2, 4, 6 };
double median = solution.FindMedian(nums1, nums2);
Console.WriteLine("The median of the two sorted arrays is: " + median);
}
}
Python
from typing import List
# 定义一个名为Solution的类
class Solution:
# 在Solution类中定义一个方法findMedianSortedArrays,它接受两个列表nums1和nums2作为输入参数,返回一个浮点数
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
# 定义一个内部函数f,它接受三个整数参数i, j, k,返回一个整数
def f(i: int, j: int, k: int) -> int:
# 如果i大于等于m(表示nums1的索引已经越界),则返回nums2在对应位置的元素
if i >= m:
return nums2[j + k - 1]
# 如果j大于等于n(表示nums2的索引已经越界),则返回nums1在对应位置的元素
if j >= n:
return nums1[i + k - 1]
# 如果k等于1(表示我们只需要再查看一个元素就能确定结果),则返回nums1和nums2在对应位置的较小值
if k == 1:
return min(nums1[i], nums2[j])
# k除以2得到p,即我们需要查看的元素的半数
p = k // 2
# 获取nums1在位置i+p-1的元素,如果索引越界,则认为它是无穷大(inf)
x = nums1[i + p - 1] if i + p - 1 < m else inf
# 获取nums2在位置j+p-1的元素,如果索引越界,则认为它是无穷大(inf)
y = nums2[j + p - 1] if j + p - 1 < n else inf
# 如果x小于y,则在i+p和j之间递归调用f函数并返回结果,否则在i和j+p之间递归调用f函数并返回结果
return f(i + p, j, k - p) if x < y else f(i, j + p, k - p)
# 获取nums1和nums2的长度,分别赋值给m和n
m, n = len(nums1), len(nums2)
# 通过f函数查找长度为(m+n+1)//2的子序列的中位数,赋值给a
a = f(0, 0, (m + n + 1) // 2)
# 通过f函数查找长度为(m+n+2)//2的子序列的中位数,赋值给b
b = f(0, 0, (m + n + 2) // 2)
# 返回a和b的平均值,即为两个有序数组的中位数
return (a + b) / 2
# 定义两个有序数组
nums1 = [1, 3, 5]
nums2 = [2, 4, 6]
# 调用函数并打印结果
result = Solution().findMedianSortedArrays(nums1, nums2)
print(result) # 输出 3.5
Java
// 定义一个名为Solution的类
class Solution {
// 定义私有整数变量m和n,分别用来存储两个输入数组的长度
private int m;
private int n;
// 定义私有整数数组nums1和nums2,用来存储输入的两个数组
private int[] nums1;
private int[] nums2;
// 定义公有方法findMedianSortedArrays,输入两个整数数组nums1和nums2,输出它们的中位数
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 将输入数组的长度分别赋值给m和n
m = nums1.length;
n = nums2.length;
// 将输入数组分别赋值给类的私有数组nums1和nums2
this.nums1 = nums1;
this.nums2 = nums2;
// 调用私有方法f,计算长度为(m+n+1)/2的子序列的中位数,存入变量a
int a = f(0, 0, (m + n + 1) / 2);
// 调用私有方法f,计算长度为(m+n+2)/2的子序列的中位数,存入变量b
int b = f(0, 0, (m + n + 2) / 2);
// 返回两个中位数的平均值,即原数组的中位数
return (a + b) / 2.0;
}
// 定义私有方法f,输入三个参数i、j、k,返回一个整数
private int f(int i, int j, int k) {
// 如果i大于等于m,说明在nums1中已经遍历完了,返回nums2在j和k位置的元素
if (i >= m) {
return nums2[j + k - 1];
}
// 如果j大于等于n,说明在nums2中已经遍历完了,返回nums1在i和k位置的元素
if (j >= n) {
return nums1[i + k - 1];
}
// 如果k等于1,说明只需要再查看一个元素就可以确定中位数,返回nums1在i位置的元素和nums2在j位置的元素的较小值
if (k == 1) {
return Math.min(nums1[i], nums2[j]);
}
// 将k除以2得到p,即需要查看的元素的半数
int p = k / 2;
// 如果i+p-1小于m,说明可以查看nums1在i+p-1位置的元素,否则该元素的值被视为一个很大的数(1 << 30)
int x = i + p - 1 < m ? nums1[i + p - 1] : 1 << 30;
// 如果j+p-1小于n,说明可以查看nums2在j+p-1位置的元素,否则该元素的值被视为一个很大的数(1 << 30)
int y = j + p - 1 < n ? nums2[j + p - 1] : 1 << 30;
// 如果x小于y,则在i+p和j之间递归调用f方法并返回结果,否则在i和j+p之间递归调用f方法并返回结果
return x < y ? f(i + p, j, k - p) : f(i, j + p, k - p);
}
}