分享

寻找两个有序数组的中位数

 算法与编程之美 2021-10-12

问题描述

给定两个大小为mn的有序数组nums1nums2,找出这两个有序数组的中位数。
示例一:
nums1=[1,3]
nums2=[2]
则中位数为2
示例二:
nums1=[12]
nums2=[34]
则中位数为(2+3/ 2 = 2.5
示例三:
nums1 = [1,3,6,8]
nums2 = [2,4,7,9,10,15]
输出结果为6.5

解决方案

当两个有序数组的长度之和为奇数的时候,中位数只有一个。
当两个有序数组的长度之和为偶数的时候,则中位数为数组合并之后位于中间的两个数的平均数。
这里我们使用二分查找求取中位数

上面第一排是长度为偶数的情况,用红色的分隔线隔开,此时中位数有两个,一个是2,一个是3分别是分割线左边最大数和分割线右边最小数。
第二排是长度为奇数的情况下,同样用红色的分隔线隔开,我们用分隔线隔开时让左边多一个元素,则分隔线左边这个元素就是长度为奇数的有序数组的中位数。(相应的,如果让右边多一个元素,则分隔线右边这个元素就是中位数,两种方式都可以)
同样的,在两个有序数组的情况下,我们也可以做一个划分,把两个有序数组划分成左边部分和右边部分,需要注意的是位于分割线左边的元素个数和位于分割线右边的元素个数应该大致相等。

1.如果两个有序数组的长度之和为偶数,则我们划分左边元素个数和右边元素个数是相等的。
2.如果两个有序数组的长度之和为奇数,则我们划分左边元素个数比右边元素个数多一个。(同理:右边比左边多一个也是可以的)
3.一个必须满足的条件:分隔线左边所有元素的数值<=分隔线右边所有元素数值。

因为两个都是有序数组,这样一来,中位数就一定只与分隔线两边黄色标记的数有关了。
当两个有序数组的长度之和为奇数时,我们让左边多一个元素

这样一来,左边最大的那个值就是两个有序数组合并之后的中位数了。
同样情况,当两个有序数组的长度之和为偶数时,我们划分两边元数个数相等,当然,左边所有元素数值<=右边所有元素数值。

如上图所示,黄色标记为合并之后数组的两个中位数,8就为左边元素最大值,而9为右边元素最小值
假设数组1的长度为m,数组2的长度为n
m+n 为偶数时,则左边元素个数为(m+n)/2
m+n 为奇数时,我们认为左边的元素个数比右边元素个数多一个,则左边元素个数为(m+n+1)/2
由于在编程语言中,除法是整数除法,而整数除法默认小数前的整数,所以当m+n为偶数时,让(m+n)/2 = (m+n+1)/2
这样我们就可以把偶数和奇数两种写法合并起来,左边元素个数就都为(m+n+1)/2。这样我们就不用对数组长度之和的奇偶性进行讨论,只需要确定一个数组划分的位置,然后通过关系计算出另一个数组划分的位置。
注意:在满足划分的情况下,还有一个条件就是在左边第一排的最大值要小于右边第二排的最小值,同样,左边第二排的最大值也要小于右边第一排的最小值,这样交叉成立,划分才成立。
解决这个问题
还存在几种特殊情况
第一种情况:较短的数组在分隔线右边没有元素。

第二种情况:较短的数组在分隔线左边没有元素。
解决办法:
首先判断第一个数组和第二数组长度,为了保证较短的数组为第一个数组
使用条件语句进行判断:if(nums1.length>nums.length){int[] nums3 = nums1; nums1 = nums2; nums2 = nums3}这样就实现了一个数组和第二个数组位置交换,
定义第一个数组分隔线右边第一个元素的下标为i
同样,定义第二个数组分隔线右边的第一个元素的小标为j
根据之前的分析,ij满足关系式:i+j=(m+n+1)/2,代表的是分隔线左边所有元素的个数。
如果nums1数组较短,那么就应该在nums1[0m]左闭右闭区间里查找恰当的分隔线。
分隔线所要满足的条件就是:前面所写的(注意
定义left = 0
Right = m
分隔线左边所有元素需要满足的个数是:totalLeft = (m+n-1)/2
i = left+(right-left)/2
j = totalLeft – i
利用循环的方法就能够找到分隔线的位置了。
代码如下:
Java寻找两个有序数组的中位数

package Solutions;

public class Solution {

    public double findMedianSortedArrays(){

        int[] nums1 = {1,3,6,8};

        int[] nums2 = {2,4,7,9,10,15};

        //为了使得分隔线在第二个数组的两侧都有元素,以至于不会出现访问时下标越界的情况

        if (nums1.length>nums2.length){

            //如果第一个数组长度大于第二个数组的长度

            //则将较短的那个数组设置成第一个数组

            int[] nums3 = nums1;

            nums1 = nums2;

            nums2 = nums3;

        }

        int m = nums1.length;

        int n = nums2.length;

        //分隔线左边的所有元素个数需要满足的个数 (m+n+1)/2

        int totalLeft = (m+n+1)/2;

        //nums1的区间[0m]里查找恰当的分割线,

        //使得nums1[i-1] <= nums2[j] &&nums[j-1] <= nums1[i]

        int left = 0;

        int right = m;

        while (left<right){

            int i = left+(right-left+1)/2;

            int j = totalLeft-i;

            if (nums1[i-1]>nums2[j]){

                //下一轮搜索的区间[left,i-1]

                right = i-1;

            }else {

                //下一轮搜索的区间是[i,right]

                left = i;

            }

        }

        int i = left;

        int j = totalLeft-i;

        int nums1LeftMax = i == 0? Integer.MIN_VALUE : nums1[i-1];

        int nums1RightMin = i == m? Integer.MAX_VALUE :nums1[i];

        int nums2LeftMax = j == 0? Integer.MIN_VALUE : nums2[j-1];

        int nums2RightMin = j == m? Integer.MAX_VALUE :nums2[j];

        if ((m+n)%2==1){

            return Math.max(nums1LeftMax,nums2LeftMax);

        }else {

            double a= (double) (Math.max(nums1LeftMax,nums2LeftMax)+Math.min(nums1RightMin,nums2RightMin))/2;

            System.out.println(a);

            return a;

        }

    }

    public static void main(String[] args) {

        Solution solution = new Solution();

        solution.findMedianSortedArrays( );

    }

}

结语

本文章简要介绍使用二分查找方法解决两个有序数组的中位数,解决这个问题还可以使用先将两个数组进行合并,然后再求取中位数。

实习编辑:王晓姣

稿件来源:深度学习与文旅应用实验室(DLETA)

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多