leetcode 4 Median of Two Sorted Arrays

Median of Two Sorted Arrays
Total Accepted: 59220 Total Submissions: 345234

There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

首先说明一下,两个数列的中位数(median),就是把两个数列合在一起后,从小到大排列,位于中间的数,如果总的长度是奇数,那么中位数就是一个数,否则中位数就是中间两个数的平均数。这个题 O(m+n) 的算法,很简单,例如:两个数列各一个指针,我们要的是总的第k小的数,那么我们就比较两个指针指向的数,指向较小的数的那个指针就往后移,指向下一个数,然后继续与另一指针指向的数比较,哪一个数小,哪一个指针就往后移。这个过程每移一次就找出一个剩下的数列中的最小数,例如第一次移动,就找出最小数,第k次移动就找出第k小的数。

但题目要求要 O(log(m+n)) , 自己苦想也不得其解,网上搜索了下,发现了别人的一种解法,有人说是 O(log(m+n)) ,我自己感觉可能是对数时间,但证明不了。不过这个解法的思路还是挺有意思的,所以记录下来。我所看的原帖地址:http://blog.csdn.net/yutianzuijin/article/details/11499917

double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) 
{
    double median;
    int total = nums1Size + nums2Size;

    if(total % 2 == 0)
    {
        median = (findKthElement(nums1, nums1Size, nums2, nums2Size, total/2) + 
        findKthElement(nums1, nums1Size, nums2, nums2Size, total/2+1)) / 2; 
    }
    else
    {
        median = findKthElement(nums1, nums1Size, nums2, nums2Size, total/2+1);
    }


    return median;
}

这个函数起始很简单,实现的功能就是之前描述的:如果总的长度是奇数,那么中位数就是第 total2+1 小的那个数,否则中位数就是中间两个数的平均数, 即: total2 total2+1 小的两个数的平均数。算法的关键还是findKthElement这个函数。

double findKthElement(int* nums1, int nums1Size, int* nums2, int nums2Size, int k)
{
    double KthElement;
    if(nums1Size > nums2Size)
        KthElement = findKthElement(nums2, nums2Size, nums1, nums1Size, k); 
    else if(nums1Size == 0)
    {
        KthElement = nums2[k-1];
    }
    else if(k == 1)
    {
        KthElement = nums1[0] < nums2[0] ? nums1[0] : nums2[0];
    }

首先我们要判断数组nums1和nums2哪个长,我们默认是nums2长于等于nums1,所以如果nums1长于nums2,那么我们就用递归的方式把两个数组交换位置。之所以保证nums1短于nums2,是因为我们后面的又操作时针对短的数组进行的,如果不确定哪个短,每次就都需要进行判断,代码就变得冗长。另外,我们要处理两种特殊情况,第一种是有一个数组为空,这是可能的,因为我们后面的算法会进行“删除”操作,因此数组可能变为空。这种情况下,我们要求第k小的元素,那显然就是剩下这个非空数组的第k小的元素,所以就是nums2[k-1],因为nums1比nums2短,所以非空的一定是nums2。另外一种情况,我们要求的是最小的元素,由于两个数列都是按升序排好了的,所以只需要比较nums1[0]和nums2[0],选择较小的那个即可。

    else
    {
        int i1 = min(nums1Size, k/2);
        if(nums1[i1-1] < nums2[k-i1-1])
        {
            KthElement = findKthElement(nums1+i1, nums1Size-i1, nums2, nums2Size, k-i1);
        } 
        else if(nums1[i1-1] > nums2[k-i1-1])
        {
            KthElement = findKthElement(nums1, nums1Size, nums2+k-i1, nums2Size-(k-i1), i1);
        }
        else
        {
            KthElement = nums1[i1-1];
        }
    }
    return KthElement;

}

其他的情况,我们要求i1,i1表示我们要求nums1中的第i1小的元素和nums2中的第k-i1小的元素,这两个元素是一定存在的,当i1等于nums1Size时,因为要求第k小,所以两数组的总数必然大于等于k,所以nums2Size必然大于等于k-nums1Size,所以nums2中必然包含大于等于k-i1(即k-nums1Size)个元素。当nums1等于 k2 时,说明nums1Size k2 , 所以nums2Size nums1Size k2 ,所以nums中必然存在第k-i1(即k- k2 )小的元素.
我们要取两者的较小值,因为我们是要在两个数组总共取k个值,所以默认是各取 k2 个,但是又可能nums1比较短,总长度都没有 k2 ,所以这个时候我们就去它的总长nums1Size,剩下的数从nums2中取,前面我已经说明了剩下的数必然可以从nums2中取出来。
我们现在已经取到了nums1中第i1小的数和nums2中第k-i1小的数,分别对应是数组中的nums1[i1-1]和nums2[k-i1-1]。然后我们来比较这两个数的大小,如果nums1[i1-1] < <script id="MathJax-Element-8544" type="math/tex"><</script>nums2[k-i1-1], 那么nums2[k-i1-1]这个数在合并后的数组中至少是第k小的,因为nums1[i1-1]比它小,说明nums1中的这前i1个数都比nums2[k-i1-1]小,而本身在nums2中已经有k-i1-1个数比它小,因此合并后至少有k-1个数比它小,因此它至少是第k小。之所以说是“至少”是因为nums1[i1-1]比它小,而nums1[i1-1]之后的其他数是否还有比它小的并不知道。
因此我们可以“删除”或者说忽略掉nums1中的这前i1个数,然后问题转化为:求从nums1中的第i1+1个数开始的剩下的数列和nums2数列合并后的第k-i1小的数。而这个数就是nums1和nums2合并后的第k小的数。这一点可以证明如下:如果求出来的这个第k-i1小的数(假设为a)来自nums1,那么它必然大于等于nums1中的前i1个数,所以把被忽略掉的这i1个数重新添加回去后,它们也都排在a的前面,所以a就变成第k小的数。如果求出来的数来自nums2,分两种情况:1. a前面k-i1个数中没有nums1的元素,那么a就是nums2里面的第k-i1小的元素,因为我们已知nums1[i1-1] nums2[k-i1-1],所以把被忽略掉的这i1个数重新添加回去后,它们也都排在a的前面,所以a就变成第k小的数。2. a前面有nums1的元素(假设其中最小的为b),因为nums1[i1-1] b a,所以把被忽略掉的这i1个数重新添加回去后,它们也都排在a的前面,所以a就变成第k小的数。

如果nums1[i1-1] > nums2[k-i1-1], 那么方法与上面的相同,只是这次被忽略掉的是nums2中的前k-i1个元素,然后在nums2从k-i1+1开始的剩余的数组和nums1中求出第i1小的元素。

另外,如果nums1[i1-1]=nums2[k-i1-1],那么这个值就是我们要找的第k小的元素。以nums1[i1-1]为例,nums2的前k-i1个元素全部可以放到它前面。nums2从第k-i1+1开始的元素全部可以放到它后面,因此它就是第k小的元素。

这个算法的好处就是每次比较后都能忽略掉一部分,使问题得规模减小,但并不是每次都减半,所以是否能够做到O(log(m+n)),我不确定。

下面附上完整代码:

#include <iostream>
#include <stdlib.h>
using namespace std;

#define min(x,y) x<y?x:y
double findKthElement(int* nums1, int nums1Size, int* nums2, int nums2Size, int k)
{
    double KthElement;
    if(nums1Size > nums2Size)
        KthElement = findKthElement(nums2, nums2Size, nums1, nums1Size, k); 
    else if(nums1Size == 0)
    {
        KthElement = nums2[k-1];
    }
    else if(k == 1)
    {
        KthElement = nums1[0] < nums2[0] ? nums1[0] : nums2[0];
    }
    else
    {
        int i1 = min(nums1Size, k/2);
        if(nums1[i1-1] < nums2[k-i1-1])
        {
            KthElement = findKthElement(nums1+i1, nums1Size-i1, nums2, nums2Size, k-i1);
        } 
        else if(nums1[i1-1] > nums2[k-i1-1])
        {
            KthElement = findKthElement(nums1, nums1Size, nums2+k-i1, nums2Size-(k-i1), i1);
        }
        else
        {
            KthElement = nums1[i1-1];
        }
    }
    return KthElement;

}

double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) 
{
    double median;
    int total = nums1Size + nums2Size;

    if(total % 2 == 0)
    {
        median = (findKthElement(nums1, nums1Size, nums2, nums2Size, total/2) + 
        findKthElement(nums1, nums1Size, nums2, nums2Size, total/2+1)) / 2; 
    }
    else
    {
        median = findKthElement(nums1, nums1Size, nums2, nums2Size, total/2+1);
    }


    return median;
}

int main()
{
    cout<<"Input the size of nums1:"<<endl;
    int nums1Size;
    cin>>nums1Size;

    int *nums1 = (int*)malloc(sizeof(int) * nums1Size);
    int temp;
    for(int i = 0; i < nums1Size; i++)
    {
        cin>>temp;
        nums1[i] = temp;
    }

    cout<<"Input the size of nums2:"<<endl;
    int nums2Size;
    cin>>nums2Size;

    int *nums2 = (int*)malloc(sizeof(int) * nums1Size);
    for(int i = 0; i < nums2Size; i++)
    {
        cin>>temp;
        nums2[i] = temp;
    }

    cout<<findMedianSortedArrays(nums1, nums1Size, nums2, nums2Size)<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值