leetcode 4. 寻找两个有序数组的中位数,c语言

leetcode上第四道题,如下。

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。

请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1 和 nums2 不会同时为空。

 题目很简洁,首先要搞懂中位数的概念,这玩意和平均数是有差别的。一开始我就搞错了,把中位数和平均数搞混了,结果做了半天结果越来越离谱。百度的中位数概念如下:

中位数是按顺序排列的一组数据中居于中间位置的数,即在这组数据中,有一半的数据比他大,有一半的数据比他小。

 如果序列个数为奇数,则中间的数就是中位数,只有一个,如果个数为偶数的话,则中位数是中间两位数的均值,需要找到两个数求平均值,如果个数为奇数,其中位数为((n+1)/2)== ((n+2)/2),对于偶数,中位数为分别为((n+2)/2)和(n/2),其中偶数情况下N/2 == (N+1)/2,因此不论奇数还是偶数,我们都可以求((n+2)/2和((n+1)/2),然后取平均值。

如果没有时间复杂度要求的话很简单,俩数组重排列后取中位数即可,但是题目要求是时间复杂度为O(log(m + n))。网上的同志说看到时间复杂度为log的基本上都是二分法,这个我之前不知道,水平不够高。看了一位大神的解法,明白了。这道题可以这样来看,求俩有序数组的中位数,相当于在一个有序的数组里找到第K个数,假设数组A的起始搜索位置和数组B的起始搜索位置分别是i和j,如果起始位置大于数组长度的话,说明在当前数组已经没有数了,这时候直接去另一个数组里取第K个数即可。如果K为1的话,返回俩数组中第一个值当中的最小值即可,如果是其它情况,我们对K进行二分,先找到K/2,假设K' = K/2,找到则排除一半,再在找另一半即可,首先判断起始位置加上要找的数长度是否超过数组长度,如果超过的话,这时候另外一个数组(假设为B)里前K' 个数是可以省略的,因为我们要找第K个数,那么它绝对不会在B的K‘ 里面,因为长度不够。举个例子,数组A={1},数组B={0,2,4,6,7,8,9,12},一共9个数,假设是有序数组,我们知道第五个数就是中位数,这里我们先二分K,5/2取余 = 2,数组A和B的初始查找位置都为0,对于A而言,2大于它的长度1,这时候我们可以排除掉数组B中的前两个元素了,因为第五个数肯定要在前两个数后面,这时候我们就排除掉两个,这时候需要移动B的起始位置,j = j + K',剩下三个,同样的道理,对三进行二分。还有一种情况是两个数组里从各自的起始位置都存在K‘个数,这时候就需要比较A[ i + K' ]和B[ j + K' ],哪个大则移动另外一个数组的起始指针K’ 位,相等则移动任意一个。这时候就排除了K‘,我们只需要递归查找第K - K',同理,继续二分,K’ = (K - K') / 2; 直到K = 1,或者某个数组起始位置超出数组长度,这时候直接返回另一个数组里对应K‘即实我们要找的值。

总的来说,思路很巧妙,没有经过训练很难短时间内想到。此外就是二分法通常都需要递归调用。

下面是该问题的C代码实现。



#define MIN(a,b) ((a) > (b) ? (b) : (a))

int GetKth(int *nums1, int nums1Size, int start1, int *nums2, int nums2Size, int start2, int k)
{
    int K1, K2;
    if (start1 + 1 > nums1Size)
        return nums2[start2 + k - 1];
    if (start2 + 1 > nums2Size)
        return nums1[start1 + k - 1];
    if (k == 1)
        return MIN(nums1[start1], nums2[start2]);
    
    if (start1 + k / 2 > nums1Size)
        return GetKth(nums1, nums1Size, start1, nums2, nums2Size, start2 + k / 2, k - k / 2);
    if (start2 + k / 2 > nums2Size)
        return GetKth(nums1, nums1Size, start1 + k / 2, nums2, nums2Size, start2, k - k / 2);
    
    K1 = nums1[start1 + k / 2 - 1];
    K2 = nums2[start2 + k / 2 - 1];
    if (K1 < K2)
        return  GetKth(nums1, nums1Size, start1 + k / 2, nums2, nums2Size, start2, k - k / 2);
    else
        return  GetKth(nums1, nums1Size, start1, nums2, nums2Size, start2 + k / 2, k - k / 2);
}

double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size){
    int k1 = (nums1Size + nums2Size + 1)/2;
    int k2 = (nums1Size + nums2Size + 2)/2;
    
    //奇数和偶数一样
    return ((double)GetKth(nums1, nums1Size, 0, nums2, nums2Size, 0, k1) + (double)GetKth(nums1, nums1Size, 0, nums2, nums2Size, 0, k2))/2;
}

参考目录:

1. https://www.cnblogs.com/grandyang/p/4465932.html

=============================================================================================

Linux应用程序、内核、驱动、后台开发交流讨论群(745510310),感兴趣的同学可以加群讨论、交流、资料查找等,前进的道路上,你不是一个人奥^_^。
 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值