算法-寻找两个有序数组的中位数

原题是力扣上的题目

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

题目不算难,但是有个问题,要求时间复杂度为O(log(m + n)),也就是说,如果我们想先统一排序,再去取中位数的操作是行不通的,因为那样的时间复杂度为O(m+n),我对这个题目思考了很久,如果想要做到O(log(m + n)),那势必每次循环都需要排除一半的可能,猜到应该是和有序有关,但是基本没有思路,因为是有可能存在【1,3,5,7,9】【2,4,6,8】这样满足有序的数组,但是我完全想不出来每次操作都排除一半可能的操作方法

想了半天,实在没有解决思路,就可耻的去看了官方的解法,官方的解法更是让人头大,各种数学公式,看的我头晕眼花,真是让人头大,绕过这些鬼话,直接看代码,终于勉强看懂了
在这里插入图片描述
首先让我们想一想中位数的本质是什么,就是一个数组中的最中间的数,我们将两个数组宛若俄罗斯套娃一般排列,中间较小的那个正方形代表较大数组中的全部值,而外面套着的这一圈就是较小数组代表的值,原谅我实在懒得重新画了,正常来说中间这条线就是中位数
在这里插入图片描述
而本题中,在最好的情况下,也确实是这样,但是最好的情况毕竟也不多见,官方题解的思路也差不多是这样,如果两个数组相交的那条线
在这里插入图片描述
大于中间这条线,那么我们可以认为,这个数显然不可能是中位数,他太大了,所以我们把较大数组的一部分填充过来,而把较小数组的一部分舍去,相反,我们就认为这个数太小了,所以我们把较大数组的一部分舍弃过来,而把较小数组的一部分加入,如果不断操作,最后可能会出现下面这几种情况
在这里插入图片描述
较小的数组都太大了,均不可能满足中位数的要求,那么我们只需要根据数组有序的前提,直接得出较大数组[(m+n)/2+1](奇数)就是中位数,相反,如果较小数组挤占了所有的空间,我们就可以认为较小数组[(m+n)/2+1](奇数)就是中位数,但是现实中显然不太可能出现这两种极端的情况,我们需要考虑一种更复杂的情况,如果较大数组和较小数组互相重叠,也就是我们之前提到的【1,3,5,7,9】【2,4,6,8】的情况,这种情况下,问题显然复杂的多,会呈现这种情况
在这里插入图片描述
中位数可能落在两个数组任意一个上,我们不能在简单的说[(m+n)/2+1]就是中位数了,那有没有办法呢,其实是有的,比如出现上面这种混乱的情况,我们只需要获取中位数在哪就行了,有两种可能,中位数在较小数组的未被排除的最后一位,或是在较大数组的未被排除的最后一位,我们只需要用较小数组的可能数组减去总的可能数组,就可以知道较大数组未被排除的最后一位数,然后获取这两个数,哪个数大,那么这个数就是中位数(奇数),因为更大的数更接近中位数,最接近的那个数就是中位数本身,官方算法的思路就是如此

public double findMedianSortedArrays(int[] A, int[] B) {
            int m = A.length;
            int n = B.length;
            将a转为为那个较小的数组
            if (m > n) { // to ensure m<=n
                int[] temp = A; A = B; B = temp;
                int tmp = m; m = n; n = tmp;
            }
            imin表示较小数组的最小位置
            imax表示较小数组的最大位置
            halfLen表示中位数出现的最大半径(即,两个数组之和的一半,向上取整),若要取(m+n)/2+1需要-1
            int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
            while (iMin <= iMax) {
            i表示当前较小数组所占的可能数
                int i = (iMin + iMax) / 2;
                j表示较大数组所占的可能数
                int j = halfLen - i;
                说明当前的较大数组的值太大了,需要填充一部分较小数组
                if (i < iMax && B[j-1] > A[i]){
                    iMin = i + 1; // i is too small
                }
                说明当前的较大数组的值太小了,需要舍弃一部分较小数组
                else if (i > iMin && A[i-1] > B[j]) {
                    iMax = i - 1; // i is too big
                }else { // i is perfect
                    int maxLeft = 0;
                    若较小数组被全部舍弃,直接到较大数组中寻找[(m+n)/2+1]
                    if (i == 0) { maxLeft = B[j-1]; }
                    若较大数组被全部舍弃,直接到较小数组中寻找[(m+n)/2+1]
                    else if (j == 0) { maxLeft = A[i-1]; }
                    否则比较两个数哪个才是真正的中位数
                    else { maxLeft = Math.max(A[i-1], B[j-1]); }
                    if ( (m + n) % 2 == 1 ) { return maxLeft; }
					下面几乎相同,只不过取的是偶数的另一半,此时正好相反,哪个数小,哪个数才是另一半
					因为此时需要求的是大于中位数的数,所以越小靠近前面的中位数
                    int minRight = 0;
                    if (i == m) { minRight = B[j]; }
                    else if (j == n) { minRight = A[i]; }
                    else { minRight = Math.min(B[j], A[i]); }

                    return (maxLeft + minRight) / 2.0;
                }
            }
            return 0.0;
       }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章