【算法面试宝典】寻找两个正序数组的中位数

1 算法描述

给定两个大小分别为 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)) 的算法解决此问题吗?

2 解题思路

解法一: 归并排序合并数组

两个有序数组找中位数,可以先把两个有序数组合并成一个有序数组,然后找出中位数即可。使用归并排序中合并数组的思路。编码实现如下:

    public static float twoSortMid(int[] nums1,int[] nums2) {
        int len = nums1.length+nums2.length;//两个数组的长度
        int[] nums3 = new int[len];//合并后的数组
        int i = 0;//数组nums1的下标
        int j = 0;//数组nums2的下标
        int k = 0;//数组nums3的下标
        float tmp;//目标值
        //遍历数组nums1和nums2,并比较元素的大小,依次放入数组nums3中
        while (i<nums1.length && j<nums2.length){
            if (nums1[i]<nums2[j]){
                nums3[k++] = nums1[i++];
            } else {
                nums3[k++] = nums2[j++];
            }
        }
        //把nums1剩余的元素放入nums3中
        while (i<nums1.length) {
            nums3[k++] = nums1[i++];
        }
        //把nums2剩余的元素放入nums3中
        while (j<nums2.length) {
            nums3[k++] = nums2[j++];
        }

        if (len%2==0) {//如果数组元素个数是偶数,目标值是中间两个元素和的平均数
            tmp = (float)(nums3[(len/2)-1] + nums3[len/2])/2;
        } else {//如果数组元素个数是奇数,目标是是中间元素
            tmp = nums3[len/2];
        }
        return tmp;
    }

解法二: 使用指针找出第k个元素

可以不合并两个数组,使用指针找出第k大的元素即可。编码实现如下所示:

    public static float twoSortMid1(int[] nums1,int[] nums2) {
        int len = nums1.length+nums2.length;
        int mid = len/2;
        float tmp1 = (float)0.0;//mid的前一位的值
        float tmp2 = (float)0.0;//mid对应的值
        float tmp;//目标值
        int i = 0;//数组nums1的下标
        int j = 0;//数组nums2的下标
        int k = 0;

        //遍历数组nums1和nums2,并比较元素的大小,依次放入数组nums3中
        while (i<nums1.length && j<nums2.length){
            if (nums1[i]<nums2[j]){
                if (k==mid){
                    tmp2 = nums1[i++];
                    break;
                } else {
                    tmp1 = nums1[i++];
                }
                k++;
            } else {
                if (k==mid){
                    tmp2 = nums2[j++];
                    break;
                } else {
                    tmp1 = nums2[j++];
                }
                k++;
            }
        }

        //把nums1剩余的元素放入nums3中
        while (i<nums1.length && tmp2<=0) {
            if (k==mid){
                tmp2 = nums1[i++];
                break;
            } else {
                tmp1 = nums1[i++];
            }
            k++;
        }

        //把nums2剩余的元素放入nums3中
        while (j<nums2.length && tmp2<=0) {
            if (k==mid){
                tmp2 = nums2[j++];
                break;
            } else {
                tmp1 = nums2[j++];
            }
            k++;
        }

        if (len%2==0) {//如果数组元素个数是偶数,目标值是中间两个元素和的平均数
            tmp = (tmp1 + tmp2)/2;
        } else {//如果数组元素个数是奇数,目标是是中间元素
            tmp = tmp2;
        }
        return tmp;
    }

解法三:二分查找找出第k个元素

这道题如果时间复杂度没有限定在 O(log(m+n)) ,我们可以用 [公式] 的算法解决,用两个指针分别指向两个数组,比较指针下的元素大小,一共移动次数为 (m+n + 1)/2,便是中位数。

首先,我们理解什么中位数:指的是该数左右个数相等。

比如:odd : [1,| 2 |,3]2 就是这个数组的中位数,左右两边都只要 1 位;

even: [1,| 2, 3 |,4]2,3 就是这个数组的中位数,左右两边 1 位;

那么,现在我们有两个数组:

num1: [a1,a2,a3,...an]

nums2: [b1,b2,b3,...bn]

[nums1[:left1],nums2[:left2] | nums1[left1:], nums2[left2:]]

只要保证左右两边 个数 相同,中位数就在 | 这个边界旁边产生。

如何找边界值,我们可以用二分法,我们先确定 num1 取 m1 个数的左半边,那么 num2 取 m2 = (m+n+1)/2 - m1 的左半边,找到合适的 m1,就用二分法找。

当 [ [a1],[b1,b2,b3] | [a2,..an],[b4,...bn] ]

我们只需要比较 b3 和 a2 的关系的大小,就可以知道这种分法是不是准确的!

例如:我们令:

nums1 = [-1,1,3,5,7,9]

nums2 =[2,4,6,8,10,12,14,16]

当 m1 = 4,m2 = 3,它的中位数就是median = (num1[m1] + num2[m2])/2

对于代码中边界情况,大家需要自己琢磨。

    public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int n1 = nums1.length;
        int n2 = nums2.length;
        if (n1>n2)
            return findMedianSortedArrays(nums2, nums1);
        int k = (n1 + n2 + 1)/2;//中位数下标
        int left = 0;
        int right = n1;
        while(left < right){
            int m1 = left +(right - left)/2;//nums1的中位数
            int m2 = k - m1;//nums2的中位数
            if (nums1[m1] < nums2[m2-1])//向右找中位数
                left = m1 + 1;
            else
                right = m1;//向左找中位数
        }
        int m1 = left;
        int m2 = k - left;
        int c1 = Math.max(m1 <= 0 ? Integer.MIN_VALUE : nums1[m1-1],
                m2 <= 0 ? Integer.MIN_VALUE : nums2[m2-1]);
        if ((n1 + n2) % 2 == 1)
            return c1;
        int c2 = Math.min( m1 >= n1 ? Integer.MAX_VALUE :nums1[m1],
                m2 >= n2 ? Integer.MAX_VALUE : nums2[m2]);
        return (c1 + c2) * 0.5;

    }

猜你喜欢

转载自blog.csdn.net/u010482601/article/details/120987957