算法基础 - 查找两个有序数组的中位数

问题描述

问题很简单,就是在两个有序的整数数组里(数组A长度为m, 数组B长度为n),找到两个数组合并后的中位数。

要求时间复杂度 O(log(m+n))

也就是说不能用先归并两个数组再直接查找的办法。

中位数

中位数就是在一个有序数组中,位于中间的数字,假如数组元素个数为偶数,则取两个中间数字的平均数。

例:
1,2,3,4,5 中位数为:3
1,2,3,4 的中位数为:2.5

算法讲解

这个问题其实看起来挺简单的,网上的一些博客里说的,很多都是一个大致思想,细节有误,代码也有误。真是误人子弟。

数组等长

假如数组等长的话,那么很简单就能解决,因为每次比较数组的中间数字,把数字大的数组扔掉右半边,把数字小的数组扔掉左半边,每次循环,到最后就能知道中位数是多少了。

But!注意事项

你以为这么简单就OK了? 当然不是!在数组等长的时候需要注意以下几个问题。

  1. 数组等长,说明数组AB的元素和为偶数,最后求的的中位数一定是两个中间数字的平均。
  2. 数组在递归的分割过程中,出现两个数组都是偶数个数的时候,比较的其实不是中间的数字(是中间偏下的数字)。因此在遇到数组如:3,4,5,6与1,2,7,8的时候,第一次比较是42发现4比较大,因此把5,6扔掉,另外一个数字扔掉1,2,但是我们可以发现,这个两个数组的中位数是4,5两个数字的平均数4.5,但是在刚才比较的过程中已经把5丢掉了。所以这个时候要注意。
  3. 数组不是在遇见A[median1] == B[median2]的时候停止。(因为1,2,3,4和1,2,4,5)在第一次比较的时候,median1 = 1, median2 = 1, 那么A[1] = 2, B[1] = 2虽然两个元素相等,但是这2并不是中位数,中位数是2.5。但是假如在比较发现相等的时候,数组比较的开始和结束是奇数个元素的话,是可以直接认为相等的。(例如: 1,2,3,4,5和1,2,3,5,7)这个两个数组的中位数就是3。

解决办法

  • 针对注意事项1:我们只用记得返回元素的时候一定是return (double)(a+b)/2就可以了。

  • 针对注意事项2:在判断两个中位数大小之后,请判断当前(因为是递归,所以每次是有开始结束位置的)是不是两个偶数个元素的中位数比较,如果是则在 A[median1] > B[median2]的时候,割掉(end1 - median1 - 1)个元素。同意数组B割掉也是这么多个元素。

  • 针对注意事项3:A: 1,2,3,4与B: 1,2,5,7在遇到A[median1] == B[median2]情况,虽然没有直接停止,但是也是已经找到中位数了,同样判断当前比较的范围是不是都是奇数个元素,如果是:返回这个数字!它就是中位数,如果不是,则返回这个数字2与其后面数字A数组2后面是3和B数组2的后面5相对较小数字3的平均数(2 + min(3,5))/2

至此已经解决了等长度的数组求中位数!

数组不等长

其实数组不等长与数组等长的解决办法基本一样!
但是还是要注意每次递归切割的长度问题。

下面的图可以简单解释一下分割的情况:
算法基础 - 查找两个有序数组的中位数_第1张图片

首先在我们切割的时候,先判断数组A和B的长短情况,哪个短,哪个是A,因为切割的时候如果以长数组为准,有可能切掉一半后,短数组早就越界访问了。

注意:图里是A[median1] < B[median2]情况,假如是A[median1] > B[median2]的情况,便如数组等长情况一样,需要判断是不是双方都是偶数个元素,如果是则切割的袁术个数不是end1 - median1而是end1 - median1 - 1个数字。

然后我们已经知道如何解决整个问题了。

递归结束条件

在数组A,B元素个数和不同情况,结束条件不同。

奇数个数

数组A和数组B的元素个数和为奇数个:

  1. A[median1] == B[median2]一定结束。
  2. 数组A只有最后一个元素,假如A[median1] < B[median2]返回B[median2]。假如A[median1] > B[median2] && A[median1] < B[median2+1]返回A[median1]。最后假如A[median1] > B[median2+1]返回B[median2+1]

以上两个是结束条件,那么有一种情况是死循环条件。就是当循环到一定程度,即数组A只剩下两个元素的时候median1 == start11,2且比B[median2]小的时候,已经没有任何切割的了。就会死循环。(其实因为A[median1] > B[median2]的时候会少切一个元素,所以不论比B[median2]大或小都需要手动切割)。
这个时候手动切割掉A中不需要的元素就好了, A[median1]小就切割掉A[median1]反之切割掉A[end1]

然后继续递归就好了,在上面已经说明了遇到A只有一个元素情况的结束条件。

注意!!!! 以上是建立在数组A和数组B总元素数量和为奇数情况。

偶数个数

偶数个数在结束条件上很麻烦!!!!
大家自己写一个例子:

[1,2];[3,4];  
---
[1,4];[2,3];
---
[1,3];[2,4,6,7];
---
[5,6];[1,2,4,7];

以上都需要一个个分析,分析A[start1]的位置和A[end1]的位置

想想什么样子的位置情况,应该是哪两个数字的平均数就可以了。不难,但是乱!

代码(C++)

以下代码在leetcode OJ上已经AC.

//
//  main.cpp
//  MedianOfTwoSortArray
//
//  Created by Alps on 15/11/2.
//  Copyright (c) 2015年 chen. All rights reserved.
//

#include 
#include 
using namespace std;

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int length1 = (int)nums1.size();
        int length2 = (int)nums2.size();
        //如果有一个数组为空
        if (length1 == 0) {
            if (length2 %2 == 0) {
                return (double)(nums2[length2/2]+nums2[length2/2 - 1])/2;
            }else{
                return (double)nums2[length2/2];
            }
        }
        //如果有一个数组为空
        if (length2 == 0) {
            if (length1 %2 == 0) {
                return (double)(nums1[length1/2]+nums1[length1/2 - 1])/2;
            }else{
                return (double)nums1[length1/2];
            }
        }

        //如果把数组长度短的放在前面
        if (length1 <= length2) {
            return findMedian(nums1, 0, length1-1, nums2, 0, length2-1);
        }else{
            return findMedian(nums2, 0, length2-1, nums1, 0, length1-1);
        }


    }
    //返回相对小的数字
    int minNumber(int a, int b){
        return a < b ? a : b;
    }
    //查找主函数
    double findMedian(vector<int>& nums1, int start1, int end1, vector<int>& nums2, int start2, int end2){
        // 如果两个数组都只有一个数字(为空的已经在之前判断过了)
        if (start1 == end1 && start2 == end2) {
            return ((double)nums1[0]+(double)nums2[0])/2;
        }
        int offset = 0;//每次切割的偏移量
        int median1 = (start1 + end1)/2;//求中间数字下标
        int median2 = (start2 + end2)/2;//求中间数字下标
        //数组A只有一个数字,而数组B不止一个数字的情况
        if (start1 == end1 && start2 != end2) {
            if ((end2 - start2) % 2 == 0) { //判断数组B如果是偶数个数字
                if (nums1[start1] >= nums2[median2-1] && nums1[start1] <= nums2[median2+1]) {
                    return (double)(nums1[start1]+nums2[median2])/2;
                }else if(nums1[start1] < nums2[median2-1]){
                    return (double)(nums2[median2-1] + nums2[median2])/2;
                }else{
                    return (double)(nums2[median2] + nums2[median2+1])/2;
                }
            }else{//如果奇数个数字
                if (nums1[start1] >= nums2[median2] && nums1[start1] <= nums2[median2+1]) {
                    return (double)nums1[start1];
                }else if(nums1[start1] < nums2[median2]){
                    return (double)nums2[median2];
                }else{
                    return (double)nums2[median2+1];
                }
            }

        }
        // 如果中间下标数字相等
        if (nums1[median1] == nums2[median2]) {
            if ((nums1.size() + nums2.size())%2 != 0 || end1-start1 % 2 == 0) {//总元素是奇数个或者两个数组范围内都是奇数个元素
                return nums1[median1];
            }else{//否则
                return (double)(nums1[median1]+minNumber(nums1[median1+1] , nums2[median2+1]))/2;
            }

        }else if (nums1[median1] < nums2[median2]){
            if (median1 == start1) {//假如A范围到两个数字的情况了
                if ((nums1.size() + nums2.size())%2 == 0) {//麻烦的判断
                    if (nums1[start1+1] >= nums2[median2+1]) {
                        return (double)(nums2[median2]+nums2[median2+1])/2;
                    }else if(median2 != 0 && nums1[start1+1] <= nums2[median2-1]){
                        return (double)(nums2[median2]+nums2[median2-1])/2;
                    }else{
                        return (double)(nums2[median2]+nums1[start1+1])/2;
                    }
                }else{
                    return findMedian(nums1, start1 + 1, end1, nums2, start2, end2-1);
                }
            }
            offset = median1-start1;//正常的offset
            return findMedian(nums1, start1+offset, end1, nums2, start2, end2-offset);
        }else{
            if (median1 == start1) {
                if ((nums1.size() + nums2.size())%2 == 0) {
                    if (nums1[end1] <= nums2[median2+1]) {
                        return (double)(nums1[median1] + nums1[start1+1])/2;
                    }else if ((nums1[end1] > nums2[median2+1] && nums1[start1] <= nums2[median2+1]) || (median2+2 < nums2.size() && nums1[median1] <= nums2[median2+2]) || (median2+2 >= nums2.size() && nums1[start1] >= nums2[median2+1])){
                        return (double)(nums1[median1]+nums2[median2+1])/2;
                    }else {
                        return (double)(nums2[median2+1]+nums2[median2+2])/2;
                    }
                }else{
                    return findMedian(nums1, start1, end1-1, nums2, start2+1, end2);
                }
            }
            offset = end1-median1;
            if ((end1-start1)%2 != 0 && (end2-start2)%2 != 0) {
                offset -= 1; //需要少切割一个的offset
            }
            return findMedian(nums1, start1, end1-offset, nums2, start2+offset, end2);
        }
        return 0;
    }
};

int main(int argc, const char * argv[]) {
    Solution sl;
    int num1[] = {1,5, 6, 7};
    int num2[] = {2,3,4,8,9,10};
    vector<int> nums1(num1, num1+4);
    vector<int> nums2(num2, num2+6);
    printf("%f\n",sl.findMedianSortedArrays(nums1, nums2));
    return 0;
}

以上就是这个问题的解决代码了。我的代码量是稍微有点大的。所以可能不是很好看。后面我看看有没有更简单的代码解决办法。

你可能感兴趣的:(leetcode,算法基础)