Leetcode #4. Median of Two Sorted Arrays (Hard) 求两个有序数组的中位数(难度-困难)

Description

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)).

Example 1

nums1 = [1, 3]

nums2 = [2]

The median is 2.0

Example 2

nums1 = [1, 2]

nums2 = [3, 4]

The median is (2 + 3)/2 = 2.5

初始分析: O(m+n)

首先,分析题目要求,该函数:输入参数为两个有序数组,输出要求为这两个数组内所有数字集的中位数。

*额外要求:算法的时间复杂度小于等于 O(log (m+n)).

先撇开额外要求不谈,碰到两个有序数组的合并,我首先想到的是归并排序(Merge Sort)的思想。归并排序本身就是不断递归地分割原始数组再进行子数组排序,最后合并这些有序子数组。而此处我只需要做有序数组的合并。


class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int[] sorted = mergeSort(nums1, nums2);
        if((sorted.length&1)==1){
            return sorted[sorted.length/2];
        }else{
            return (sorted[sorted.length/2 - 1] + sorted[sorted.length/2])/2.0;
        }
    }
    
    public int[] mergeSort(int[] a, int[] b){
        int[] output = new int[a.length + b.length];
        int lt = 0;
        int rt = 0;
        int index = 0;
        while(lt<a.length && rt<b.length){
            if(a[lt]<b[rt]){
                output[index++] = a[lt++];
            }else{
                output[index++] = b[rt++];
            }
        }
        if(lt!=a.length){
            System.arraycopy(a, lt, output, lt+rt, a.length-lt);
        }else{
            System.arraycopy(b, rt, output, lt+rt, b.length-rt);
        }
        return output;
    }
}

基于这个想法的代码大概长这样(有可以改进的地方欢迎提出)。
时间复杂度为O(m+n),能被AC,打败了40%的java算法。

深度分析: O(log(min(m,n))),思路参考LeetCode网友MissMary

首先理解中位数的代数意义:

中位数, 统计学中的专有名词,代表一个样本、种群或概率分布中的一个数值,其可 将数值集合划分为长度相等的上下两部分。

关键在于后半句: 将数值集合划分为长度相等的上下两部分
除此之外,对于划分后的两组数字,其中一组内的数字总是大于另一组内的数字。

举个例子,先把第一个数组(称为数组A)在随机位置 i 处分割开:

左半边 右半边
A[0], A[1],....,A[i-1] A[i], A[i+1],...,A[m-1]

同样地,在随机位置 j 处 将数组B分割开:

左半边 右半边
B[0], B[1],....,B[j-1] B[j], B[j+1],...,B[n-1]

将A、B数组的左半边放到一起,右半边放到一起, 可得:

左半边 右半边
A[0], A[1],....,A[i-1] A[i], A[i+1],...,A[m-1]
B[0], B[1],....,B[j-1] B[j], B[j+1],...,B[n-1]

如果这样的分组能满足下面两个条件:

1. len(左半边)==len(右半边)
2. max(左半边)<=min(右半边)

就说明我们就成功的将{A,B}中的所有元素分割成了等长的两部分,且左半边的数字总小于右半边的数字。
这时,中位数可以很轻易地通过 Median = (max(左半边)+min(右半边))/2 计算出来。

为了满足这两个条件,我们只需要保证:

1). 左右两部分等长:
    i + j == m - i + n - j (或者: m - i + n - j + 1)
    假设 n >= m, 只需要让: i = 0 ~ m, j = (m + n + 1)/2 - i
2). B[j-1] <= A[i] and A[i-1] <= B[j]

为什么需要假设n>=m? 因为如果m>n, 则B数组的下标索引 j 有可能变成负数。
对于 i 和 j 等于0的边缘情况,留到后面讨论
所以,简而言之,程序需要做的仅仅是:

在[0, m]中寻找合适的分割点 i , 使得:
    B[j-1] <= A[i] and A[i-1] <= B[j], ( 其中 j = (m + n + 1)/2 - i )

这个过程可以通过二分查找法提高效率:

<1> 设 imin = 0, imax = m, 然后在 [imin, imax] 中进行查找

<2> 设 i = (imin + imax)/2, j = (m + n + 1)/2 - i

<3> 接下来有三种情况:
    <a> B[j-1] <= A[i] and A[i-1] <= B[j]
        //找到了合适的分割点 i, 停止搜索
    <b> B[j-1] > A[i]
        //A[i] 的值偏小. 需要调整 i 使得 `B[j-1] <= A[i]`.
        //此处应该增大 i 值,因为当 i 值增大时, j 值会减小.
        //因此B[j-1]会随A[i]的增大而减小, 更容易满足 `B[j-1] <= A[i]`
        //所以应将搜索范围调整为 [i+1, imax]. 即: 使 imin = i+1, 然后回到步骤 <2>.
    <c> A[i-1] > B[j]
       //与前一种情况的处理方式相反,即: 使 imax = i-1,然后回到步骤<2>.

当找到合适的分割点 i 后,易知中位数为:

当 m + n 为奇数:max(A[i-1], B[j-1])
当 m + n 为偶数:(max(A[i-1], B[j-1]) + min(A[i], B[j]))/2

再来考虑边缘情况,即: 当 i=0,i=m,j=0,j=n 时 A[i-1],B[j-1],A[i],B[j] 有可能不存在的情况:
其实这些情况非常容易分析,因为我们所要满足的仅仅是 max(左半边) <= min(右半边) 这个条件,假设A[i-1],B[j-1],A[i],B[j]都存在,则该条件可表示为 B[j-1] <= A[i] and A[i-1] <= B[j]。如果有些值不存在的话则完全不用去判断,比如我们假设 i=0, 则 A[i-1]不存在,我们便不用判断 A[i-1] <= B[j]这个条件,因为此时数组A不存在左半边。所以,考虑边缘情况后,搜索思路大致为:

在 [0, m] 中寻找合适的分割点 i, 使得:
    (j == 0 or i == m or B[j-1] <= A[i]) and
    (i == 0 or j == n or A[i-1] <= B[j]),
    其中 j = (m + n + 1)/2 - i

在搜索的过程中,会出现下面三种情况:

<a> (j == 0 or i == m or B[j-1] <= A[i]) and
    (i == 0 or j = n or A[i-1] <= B[j])
    //找到合适的 i 值,停止搜索

<b> j > 0 and i < m and B[j - 1] > A[i]
    // i 值偏小,需要增加 i 值

<c> i > 0 and j < n and A[i - 1] > B[j]
    // i 值偏大,需要减小 i 值

其中<b>和<c>的判断条件可以进一步简化为

<b> i < m and B[j - 1] > A[i]
<c> i > 0 and A[i - 1] > B[j]

因为当 i < m时,j一定大于0,且 i>0 时,j一定小于n,推导过程为:

m <= n, i < m ==> j = (m+n+1)/2 - i > (m+n+1)/2 - m >= (2*m+1)/2 - m >= 0    
m <= n, i > 0 ==> j = (m+n+1)/2 - i < (m+n+1)/2 <= (2*n+1)/2 <= n

基于上述的思路的Java代码如下:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) 
    {
        int m = nums1.length;
        int n = nums2.length;
        if(m>n)
        {
            int[] tmp = nums1;
            nums1 = nums2;
            nums2 = tmp;
            m=m^n;
            n=m^n;
            m=m^n;
        } //交换m与n的值,以及nums1和nums2的引用
        int imin = 0;
        int imax = m;
        int half = (m+n+1)>>1;
        
        while(imin<=imax)
        {
            int i = (imin + imax)>>1;
            int j = half - i;
            
            if(i<m && nums2[j-1]>nums1[i])
                imin = ++i; //i值偏小
            else if(i>0 && nums1[i-1]>nums2[j])
                imax = --i; //i值偏大
            else //完美情况
            {
                int max_left = 0;
                
                if(i==0)
                    max_left = nums2[j-1];
                else if(j==0)
                    max_left = nums1[i-1];
                else
                    max_left = Math.max(nums1[i-1], nums2[j-1]);
                
                if(((m+n)&1)==1)
                    return max_left; //m+n为奇数的情况中位数为max_left
                
                int min_right = 0;
                
                if(i==m)
                    min_right = nums2[j];
                else if(j==n)
                    min_right = nums1[i];
                else
                    min_right = Math.min(nums1[i], nums2[j]);
                return (max_left+min_right)/2.0;  //m+n为偶数的情况中位数为(max_left+min_right)/2
            }
        }
        return -1;
    }
}

上述算法的时间复杂度为O(log(min(m,n))), 代码部分有可以改进的地方欢迎提出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,724评论 0 33
  • 一年级语文上册生字表 生字表一(共400字) 啊(ā)爱(ài)安(ān)岸(àn)爸(bà)八(bā)巴(bā)...
    meychang阅读 2,760评论 0 6
  • 有时有些心伤, 也不知道是何缘故。 只得任由其发展 不去理会,却越发的沉沦下去。 直至自己发觉不能够再如此才停止。...
    浦和0917阅读 179评论 0 2
  • 文来自微信公众号拆书帮(ID:chaishubang) 请输入图片描述 ​ 入职第一年的表妹向我哭诉,说她的领导对...
    拆书帮阅读 266评论 0 0
  • 9.12晚23:30深圳飞曼谷——9.15上午曼谷飞清迈——9.18上午清迈飞曼谷——9.18曼谷飞深圳。花费50...
    是hoho呀阅读 246评论 0 0