【LeetCode】寻找两个有序数组的中位数【性质分析+二分】

给定两个大小为 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

来源:力扣(LeetCode)
链接:
https://leetcode-cn.com/problems/median-of-two-sorted-arrays

分析:

QQ图片20190731190230

m为数组A元素数量

n为数组B元素数量

通过上图我们可以得知:

1.在合并后的大数组中,中位数的作用就是把数组分成元素数量相同的两部分,这两部分的元素是连续的,并且右侧的元素大于等于或者左侧的元素(也就是橙色元素大于或者等于绿色元素)

2.大数组中的元素不是来自于数组A就是来自于数组B,也就是说,数组A和数组B肯定是由分割线两侧的元素混合构成的(先不考虑特殊情况),由于他们都是有序数组,那么数组A和数组B中肯定也存在两条这样的分割线i和j,我们只需要在A数组和B数组中找到确切的i分割线和j分割线的位置,就可以确定大数组中分割线的位置,从而就可以确定中位数的位置

3.那么怎么寻找合适的i和j呢?

i和j满足的要求:i+j=(n+m+1)/2   (+1是为了保证元素总数量无论是奇数还是偶数该公式都成立)

根据公式知道,i和j只要确定了一个,另外一个也就确定了,所以我们只需要在数组A中寻找合适的i,那什么样的i才是合适的i呢?

QQ图片20190731195052

合适的i和j必须要满足以下要求:

1)A[i]>=B[j-1]

2)B[j]>=A[i-1]

也就是保证所有橙色元素都大于或者等于绿色元素,换句话说就是为了保证大数组中右侧元素都大于或者等于左侧元素,只有这样的i和j才是合适的,才可以根据i和j确定大数组中位数的位置

那么当i和j不合适时,我们应该怎么调整呢?我们调整i,j也会随着变化,所有我只对i进行调整就好

当A[i]<B[j-1]时:说明i太小了,i应该右移

当B[j]<A[i-1]时:说明i太大了,i应该左移

我们可以通过二分的方式来移动i

当找到合适的i和j后

如果总元素数量为奇数,那么左侧最大元素max(A[i-1],B[j-1])就是中位数

如果总元素数量为偶数,那么左侧最大元素和右侧最小元素的平均值就是中位数

ps:右侧最小元素=min(A[i],B[j])

需要处理几种特殊情况:

1)如果B元素数量比A元素数量少的话,通过i得到的j值在数组B中可能会越界

解决方案:如果数组A的元素数量比数组B的元素数量多,那么交换A,B数组的元素,也就是说,i是在数组元素数量少的数组上移动的,这样通过i得到的j值在B数组肯定不会越界

2)i等于0的情况

这种情况下,i-1会越界,那么左侧的最大元素为B[j-1]就好

3)j等于0的情况

这种情况下,j-1会越界,那么左侧的最大元素为A[i-1]就好

4)i等于m的情况

这种情况下,A[m]元素取不到,也越界了,那么右侧最小元素为B[j]就好

5)j等于n的情况

这种情况下,B[j]元素取不到,也越界了,那么右侧最小元素为A[i]就好

时间复杂度分析:对A数组进行二分寻找合适的i,又因为A数组是元素数量最少的数组,所以该算法的时间复杂度为:O(log (min(m,n)))

空间复杂度:O(1)

另外一篇也很不错的博文:https://mp.weixin.qq.com/s/OE4lHO8-jOIxIfWO_1oNpQ

code:

复制代码
double findMedianSortedArrays(vector<int>& A, vector<int>& B)
{
    int m=A.size();
    int n=B.size();
    if(m>n)//i指向A数组,A为短数组可以避免j越界
    {
       swap(A,B);
       swap(n,m);
    }
    int low=0;
    int high=m;
    int k=(m+n+1)/2;
    while(low<=high)//二分A数组
    {
        int i=(low+high)/2;//i指向A数组
        int j=k-i;//j指向B数组
        if(i<high&&A[i]<B[j-1])//i太小,i需要右移
        {
            low=i+1;
        }else if(i>low&&A[i-1]>B[j])//i太大,i需要左移
        {
            high=i-1;
        }else//找到了合格的i,j
        {
            int maxleft;
            //特殊情况
            if(i==0)
            {
                maxleft=B[j-1];
            }else if(j==0)
            {
                maxleft=A[i-1];
            }else
            {
                maxleft=max(A[i-1],B[j-1]);//获得左侧最大值
            }
            if((m+n)%2==1)//如果两个数组的元素数量为奇数,那么左侧的最大值就是中位数
                return maxleft*1.0;
            int minright;
            //特殊情况
            if(i==m)
            {
                minright=B[j];
            }else if(j==n)
            {
                minright=A[i];
            }else
            {
                minright=min(A[i],B[j]);//获得右侧最小值
            }
            return (maxleft+minright)/2.0;//元素总数量为偶数,那么中位数等于左侧最大值和右侧最小值的平均值
        }
    }
    return 0.0;
}
复制代码

 

golang实现:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
func max(x, y int) int {
    if x > y {
        return x
    }
    return y
}
func min(x, y int) int {
    if x < y {
        return x
    }
    return y
}
func findMedianSortedArrays(a []int, b []int) float64 {
    m := len(a)
    n := len(b)
 
    // 保证a数组为短数组,避免j越界
    if m > n {
        a, b = b, a
        m, n = n, m
    }
 
    // i为a数组中点,j为b数组中点,i和j具有关系:i+j=(m+n+1)/2
    // 第一步:在a数组中采用二分的方式寻找合适的i,满足以下关系: a[i]>=b[j-1] && b[j]>=a[i-1]
    k := (m + n + 1) / 2
 
    l := 0
    h := m
    var i,j int
    for l <= h {
        i = (l + h) / 2
        j = k - i // 通过i可以确定j的值
        if i <h && a[i] < b[j-1] {
            // i太小了,i需要增大,取二分的右半边
            l = i + 1
        } else if i > l && b[j] < a[i-1] {
            // i太大了,i需要减小,去二分的左半边
            h = i - 1
        }else  {
            // 寻找到了合适的i
            // i可能等于0或m ,j可能等于0或n 所以不要操作数组,避免越界错误
            break
        }
    }
 
    // 第二步:寻找到了合适的i,根据元素总数量的奇偶性判断如何取中位数
 
    // 情况1:元素总数量是奇数,那么左侧的最大元素max(a[i-1],b[j-1])就是中位数
    var leftMaxValue int
    // 特殊处理i等于0或者j等于0的特殊情况
    if i == 0 {
        leftMaxValue = b[j-1] // i-1越界,只取b[j-1]
    } else if j == 0 {
        leftMaxValue = a[i-1] // j-1 越界,只取a[i-1]
    } else {
        leftMaxValue = max(a[i-1], b[j-1])
    }
    if (n+m)%2 ==1 {
        return float64(leftMaxValue)
    }
 
    // 情况2:元素总数量是偶数,那么左侧最大元素max(a[i-1],b[j-1])和右侧最小元素min(a[i],b[j])的平均值就是中位数
    var rightminValue int
    // 特殊处理i等于m或j等于n的特殊情况
    if i == m {
        rightminValue = b[j] // i越界,只取b[j]
    } else if j == n {
        rightminValue = a[i] // j越界,只取a[i]
    } else {
        rightminValue = min(a[i], b[j])
    }
    return (float64(leftMaxValue) + float64(rightminValue)) / 2
 
}

 

  

 

另外一种时间复杂度稍微差点的方法

将求中位数转化为求第k大数,当k=(m+n+1)/2时,为原问题的解,那么怎么求两个数组的第k大数呢?

分别求出A数组和B数组的第k/2个数x和y,然后比较x,y

当x<y时,说明第k个数位于A数组的第k/2个数的后半段

当x>y时,说明第k个数位于B数组的第k/2个数的前半段

问题规模缩小了一般,然后递归处理就行了(特殊情况的细节没有说明,这里只讲解一下大概思路,因为该方法时间复杂度较高,为O(log(m+n))

具体请参考:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/zhen-zheng-ologmnde-jie-fa-na-xie-shuo-gui-bing-pa/

 golang实现

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
func max(x, y int) int {
    if x > y {
        return x
    }
    return y
}
func min(x, y int) int {
    if x < y {
        return x
    }
    return y
}
 
 
 
func findKth(a []int,aStart int,b []int,bStart int,k int) float64{
    // 任何一个数组为空,取另外一个数组的第k个数
    if aStart>=len(a){
        return float64(b[bStart+k-1])
    }
    if bStart>=len(b){
        return float64(a[aStart+k-1])
    }
 
    // k等于1,取两个首元素中小的那个
    if k==1{
        return float64(min(a[aStart],b[bStart]))
    }
 
    x,y:=math.MaxInt64,math.MaxInt64
 
    // 分别取a,b数组的第k/2个元素 x和y
    if aStart+k/2-1<len(a){
        x=a[aStart+k/2-1]
    }
    if bStart+k/2-1<len(b){
        y=b[bStart+k/2-1]
    }
 
    if x<y{
        // x小于y,第k个数位于a数组的第k/2个数的后半段,缩小区间 继续查找
        return findKth(a,aStart+k/2,b,bStart,k-k/2)
    }
 
    // x大于y,第k个数位于b数组的第k/2个数的前半段
    return findKth(a,aStart,b,bStart+k/2,k-k/2)
 
}
func findMedianSortedArrays(a []int, b []int) float64 {
    m:=len(a)
    n:=len(b)
 
    // 处理任何一个数组为空的情况
    if m==0{
        if n%2==0{
            return (float64(b[n/2])+float64(b[n/2-1]))/2
        }
        return float64(b[n/2])
    }
    if n==0{
        if m%2==0{
            return (float64(a[m/2])+float64(a[m/2-1]))/2
        }
        return float64(a[m/2])
    }
 
    total:= m+n
 
    // 总数为奇数,寻找第k个数 k=total/2+1
    if (m+n)%2==1{
        return findKth(a,0,b,0,total/2+1)
    }
 
    // 总数为偶数,寻找 第total/2个 和 第tatal/2+1个 数的平均值
    x:=findKth(a,0,b,0,total/2)
    y:=findKth(a,0,b,0,total/2+1)
    return (x+y)/2.0
}

 

  

 

 

 

posted @   西*风  阅读(416)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
历史上的今天:
2018-07-31 HDU 1045 Fire Net(DFS 与8皇后问题类似)
2018-07-31 HDU 1009 FatMouse' Trade(简单贪心)
2018-07-31 CSU-ACM2018暑期训练7-贪心
2018-07-31 HDU 1735 字数统计(模拟+一点点贪心的思想)
2018-07-31 HDU 4864 Task(经典贪心)
2018-07-31 51Nod - 1205 (流水先调度)超级经典的贪心 模板题
2018-07-31 POJ 3122 Pie(二分+贪心)
点击右上角即可分享
微信分享提示