LeetCode4 寻找两个正序数组的中位数
题目
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
示例 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
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106
进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?
方法
方法1:合并两个数组,找中位数
时间复杂度:
空间复杂度:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int l1 = nums1.length;
int l2 = nums2.length;
if(l1==0){
if(l2%2 == 0){
return (nums2[l2/2]+nums2[l2/2-1])/2.0;
}
if(l1%2 != 0){
return nums2[l2/2];
}
}
if(l2==0){
if(l1%2 == 0){
return (nums1[l1/2]+nums1[l1/2-1])/2.0;
}
if(l1%2 != 0){
return nums1[l1/2];
}
}
int l = l1+l2;
int[] sum = new int[l];
int i=0;
int j=0;
int count = 0;
while(count<(l1+l2)){
if(i==l1){
while(j<l2){
sum[count++] = nums2[j++];
}
break;
}
if(j==l2){
while(i<l1){
sum[count++] = nums1[i++];
}
break;
}
if(nums1[i]<nums2[j]){
sum[count++] = nums1[i++];
}else{
sum[count++] = nums2[j++];
}
}
if(l%2==0){
return (sum[l/2]+sum[l/2-1])/2.0;
}else{
return sum[l/2];
}
}
}
方法2:不合并直接找中位数
时间复杂度:O(m+n)
空间复杂度:O(1)
Java版本:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int l1 = nums1.length;
int l2 = nums2.length;
int l = l1+l2;
int left = -1,right = -1;
int index1 = 0,index2 = 0;
for(int i=0;i<=l/2;i++){
left = right;
if(index1<l1 && (index2>=l2 || nums1[index1]<nums2[index2])){
right = nums1[index1++];
}else{
right = nums2[index2++];
}
}
if((l&1)==0){
return (left+right)/2.0;
}else{
return right;
}
}
}
JavaScript版本:
var findMedianSortedArrays = function(nums1, nums2) {
const len1 = nums1.length,len2 = nums2.length;
const len = len1+len2;
let index1 = 0,index2 = 0;
let left = -1,right = -1;
for(let i=0;i<=len/2;i++){
left = right;
if(index1<len1&&(index2>=len2||nums1[index1]<nums2[index2])){
right = nums1[index1++];
}else{
right = nums2[index2++];
}
}
if(len%2===0){
return (left+right)/2;
}
return right;
};
二分查找法
假设长度为m的num1和长度为n的num2分别存在下标i和j之前的分割线,将分割线左边的数的个数分为总长的一半,并且分割线左边的数总小于等于右边,即可找到中位数:
- 若总长为偶数,则分割线左边的最大值和分割线右边的最小值为中位数,
- 若总长为奇数,则分割线左边的最大值为中位数;
因此条件可表述为:
偶数情况:i+j=m-i+n-j,num1[i-1]<=num2[j],num2[j-1]<=num1[i]
奇数情况:i+j=m-i+n-j+1,num1[i-1]<=num2[j],num2[j-1]<=num1[i]
第一个条件可变为i+j = (m+n+1)/2,只取整数,假设m<n,因此j=(m+n+1)/2-i
第二个条件num1[i-1]<=num2[j],num2[j-1]<=num1[i],由于i增加时num1增大,num2减小,因此存在临界点使num1[i-1]<=num2[j],但nums[i]确不满足,因此num2[j-1]<=num1[i]自然成立,因此第二个条件变为找到最大的i满足num1[i-1]<=num2[j]
综上所述,所需的条件即为:
1)保证m<n
2)j=(m+n+1)/2-i,只取整数
3)找到最大的i使num1[i-1]<=num2[j]
其中,有以下论证:
论证1:为什么必须保证m<n?
m>n的话,j有可能是负的,因为j= (m+n+1)/2-i,因此i增大则j减小,i最大为m-1时,j=(n-m-1)/2,此时若m>n则j是负的
论证2:由于i增加时num1增大,num2减小
因为j= (m+n+1)/2-i,i增大则j减小,而两个数组都是递增的,所以i增大则j减小,对应的num1增大num2减小
- 时间复杂度:O(log(min(m,n)),即对长度小的数组二分
- 空间复杂度:O(1)
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length,n = nums2.length;
if(m>n){
return findMedianSortedArrays(nums2,nums1);
}
int left = 0,right = m;
int max_left = 0,min_right = 0;
while(left<=right){
int mid_i = (left+right)/2;
int mid_j = (m+n+1)/2-mid_i;
int num1_i_1 = mid_i==0?Integer.MIN_VALUE: nums1[mid_i-1];
int num1_i = mid_i==m?Integer.MAX_VALUE : nums1[mid_i];
int num2_j_1 = mid_j==0?Integer.MIN_VALUE : nums2[mid_j-1];
int num2_j = mid_j==n?Integer.MAX_VALUE : nums2[mid_j];
if(num1_i_1<=num2_j){
max_left = Math.max(num1_i_1,num2_j_1);
min_right = Math.min(num1_i,num2_j);
left = mid_i+1; //A[i-1]<=B[j].说明临界点在右边
}else{
right = mid_i-1;//A[i-1]>B[j].说明临界点在左边
}
}
return (m+n)%2==0? (max_left+min_right)/2.0 : max_left;
}
}
分类:
LeetCode Hot100
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理