算法题之--《寻找两个有序数组的中位数》

这是LeetCode上的一道算法题

给定两个有序数组,求两个有序数组的中位数,要求时间复杂度O(log(m + n))

看到时间复杂度带log,那就不是简单的合并排序直接获取中位数这么简单了。第一反应能想到的就是最直接有效的二分法,这样才能把时间复杂度降下来。在开始图解之前,我们先来看下简单的例子:

比如:

arr1: 1 3 5
arr2: 2 4 6 7 8

这里一共有8个元素,中位数要根据他俩合并后中间的第4,第5个元素来得到,也就是:(4 + 5)/ 2 = 4.5
问题就简化为找到第4小和第5小的元素。以寻找第4小为例:
第4小有可能藏在arr1和arr2的前两个元素里,并且是其中最大的,也有可能不在里面。所以,只要我们不删除arr1和arr2前两个元素中的最大元素,第4小就会继续留在两个数组里。

arr1: 1 3 | 5
arr2: 2 4 | 6 7 8
通过比较arr1和arr2的第2个元素: 3 < 4; 说明第4小肯定不在arr1的 【1 3】中间,因为第4小如果在【1 3】和【2 4】中间的话,它一定是最大的,所以即使我们移除【1 3】,第4小仍然会留在两个数组中,但是移除【1 3】后我们要查找的范围就缩小了一半,好处多多

下面,我们看下一个更详细例子的图解:


两个有序数组寻找中位数

理解了上面的解析后,我们还要注意下下面几种特殊情况:

特殊情况

这几种情况都是我们要在代码中需要注意的地方,下面直接看示例代码:

@implementation MediaOfTwoArr

+(CGFloat)mediaInArr:(NSMutableArray *)arr1 andArr:(NSMutableArray *)arr2 {
    
    NSInteger count1 = arr1.count;
    NSInteger count2 = arr2.count;
    
    /**
     求中位数,只需要得到两个数组合并后中间的一个或者两个数字即可
     
     比如偶数情况下:一共14个数字,则中位数=(第7小数 + 第8小数)/2;
     奇数情况下:比如一共15个数字,则中位数 = 第8个小数;
     */
    
    // 根据两个数组的总个数,计算要得到的第k小数和第k+1小数
    NSInteger kMin = 0;
    if ((count1 + count2) % 2 == 0) {
        // 偶数情况下需要得到中间的两个数才能求的中位数,这里我们直接放到数组中
        kMin = (count1 + count2) / 2;
        NSMutableArray *result = [NSMutableArray array];
        [MediaOfTwoArr getKminAndK1minInArr:arr1
                                   withArr2:arr2
                        withLeftIndexOfArr1:0
                        withLeftIndexOfArr2:0
                                   withKmin:kMin
                                 withResult:result];
        return ([result[0] floatValue] + [result[1] floatValue]) / 2;
        
    } else {
        // 奇数情况下,只需要得到最中间的数就可以了
        kMin = (count1 + count2 + 1) / 2;
        return [MediaOfTwoArr getKminInArr:arr1
                           withArr2:arr2
                withLeftIndexOfArr1:0
                withLeftIndexOfArr2:0
                           withKmin:kMin];
    }
}

+(void)getKminAndK1minInArr:(NSMutableArray *)arr1
                withArr2:(NSMutableArray *)arr2
     withLeftIndexOfArr1:(NSInteger)arr1Left
     withLeftIndexOfArr2:(NSInteger)arr2Left
                withKmin:(NSInteger)kmin
                 withResult:(NSMutableArray *)result {
    
    if (arr1Left >= arr1.count) { // arr1已经遍历完,则直接从arr2当前索引开始取第kmin小数即可
        [result addObject:arr2[arr2Left + kmin - 1]]; // 第k小数
        [result addObject:arr2[arr2Left + kmin]]; // 第k + 1小数
        return;
    }
    
    if (arr2Left >= arr2.count) { // 同上
        [result addObject:arr1[arr1Left + kmin - 1]];
        [result addObject:arr1[arr1Left + kmin]];
        return;
    }
    
    if (kmin == 1) {
        // 当kin为1时,直接比较arr1和arr2当前索引的元素大小即可得到要求的小数
        if ([arr1[arr1Left] integerValue] <= [arr2[arr2Left] integerValue]) {
            [result addObject:arr1[arr1Left]]; // 第k小数
            
            // 如果arr1中只剩下第k小数了,那么k + 1小数一定在arr2中
            if (arr1Left == arr1.count - 1) {
                [result addObject:arr2[arr2Left]];
                
                // 否则继续比较arr1下一个元素和arr2的当前索引,得到第k + 1小数
            } else {
                if ([arr1[arr1Left + 1] integerValue] < [arr2[arr2Left] integerValue]) {
                    [result addObject:arr1[arr1Left + 1]];
                } else {
                    [result addObject:arr2[arr2Left]];
                }
            }
        } else {
            [result addObject:arr2[arr2Left]]; // 第k小数
            
            // 如果arr2中只剩下第k小数了,那么k + 1小数一定在arr1中
            if (arr2Left == arr2.count - 1) {
                [result addObject:arr1[arr1Left]];
                
                // 否则继续比较arr1下一个元素和arr2的当前索引,得到第k + 1小数
            } else {
                if ([arr2[arr2Left + 1] integerValue] < [arr1[arr1Left] integerValue]) {
                    [result addObject:arr2[arr2Left + 1]];
                } else {
                    [result addObject:arr1[arr1Left]];
                }
            }
        }
        
        return;
    }
    
    // 比较arr1和arr2 从当前索引开始的第kmin/2个元素大小,这里向下取整
    NSInteger removeCount = kmin / 2;
    
    // 第kmin/2个元素超出arr1的范围,则将removeCount直接设为arr1剩余元素的个数
    if (removeCount + arr1Left > arr1.count) {
        removeCount = arr1.count - arr1Left;
    } else if (removeCount + arr2Left > arr2.count) { // 同上
        removeCount = arr2.count - arr2Left;
    }
    
    if ([arr1[removeCount + arr1Left - 1] integerValue] <= [arr2[removeCount + arr2Left - 1] integerValue]) {
        // arr1比较小,将arr1Left右移继续递归
        arr1Left = arr1Left + removeCount;
    } else {
        // arr2比较小,将arr2Left右移继续递归
        arr2Left = arr2Left + removeCount;
    }
    
    return [MediaOfTwoArr getKminAndK1minInArr:arr1
                                      withArr2:arr2
                           withLeftIndexOfArr1:arr1Left
                           withLeftIndexOfArr2:arr2Left
                                      withKmin:kmin - removeCount
                                    withResult:result];
}

+(NSInteger)getKminInArr:(NSMutableArray *)arr1
           withArr2:(NSMutableArray *)arr2
withLeftIndexOfArr1:(NSInteger)arr1Left
withLeftIndexOfArr2:(NSInteger)arr2Left
           withKmin:(NSInteger)kmin {
    
    // arr1已经遍历完,则直接从arr2当前索引开始取第kmin小数即可
    if (arr1Left >= arr1.count) {
        return [arr2[arr2Left + kmin - 1] integerValue];
    }
    
    if (arr2Left >= arr2.count) { // 同上
        return [arr1[arr1Left + kmin - 1] integerValue];
    }
 
    // 当kin为1时,直接比较arr1和arr2当前索引的元素大小即可得到要求的小数
    if (kmin == 1) {
        if ([arr1[arr1Left] integerValue] <= [arr2[arr2Left] integerValue]) {
            return [arr1[arr1Left] integerValue];
        } else {
            return [arr2[arr2Left] integerValue];
        }
    }
        
    // 比较arr1和arr2 从当前索引开始的第kmin/2个元素大小
    NSInteger removeCount = kmin / 2;
    
    // 第kmin/2个元素超出arr1的范围,则将removeCount直接设为arr1剩余元素的个数
    if (removeCount + arr1Left > arr1.count) {
        removeCount = arr1.count - arr1Left;
    } else if (removeCount + arr2Left > arr2.count) { // 同上
        removeCount = arr2.count - arr2Left;
    }
    
    if ([arr1[removeCount + arr1Left - 1] integerValue] <= [arr2[removeCount + arr2Left - 1] integerValue]) {
        // arr1比较小,将arr1Left右移继续递归
        arr1Left = arr1Left + removeCount;
    } else {
        // arr2比较小,将arr2Left右移继续递归
        arr2Left = arr2Left + removeCount;
    }
    
    return [MediaOfTwoArr getKminInArr:arr1
                     withArr2:arr2
          withLeftIndexOfArr1:arr1Left
          withLeftIndexOfArr2:arr2Left
                     withKmin:kmin - removeCount];
}

@end

这里为了方便观察,我把数组个数是奇数和偶数两种情况分开处理了,奇数时,只需要获取第k小数即可;偶数时,需要获取第k小数和第k+1小数。

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

推荐阅读更多精彩内容

  • 第四天 数组【悟空教程】 第04天 Java基础 第1章数组 1.1数组概念 软件的基本功能是处理数据,而在处理数...
    Java帮帮阅读 1,531评论 0 9
  • 第五章******************************************************...
    fastwe阅读 639评论 0 0
  • //JS数组常用方法及其应用/** * 1.push(): 向数组尾部添加一个或多个元素,并返回添加新元素后的数组...
    李二狗的星球阅读 408评论 0 0
  • 分析 已知两个有序数组,找到两个数组合并后的中位数。 解法一 简单粗暴,先将两个数组合并,两个有序数组的合并也是归...
    zzpwestlife阅读 245评论 0 2
  • 推开一窗春色 看二月流云 万里舒卷 守住一院花香 听三月小雨 轻唱呢喃 斟起一盏暖阳 赏四月芳菲 乱红迷眼 ...
    嘀嗒72阅读 184评论 0 3