面试算法:快速获取数组中点的相邻区域点

假设给定一个数组A,要求你找到数组的中点,并且将离中点最近的k个数组成员抽取出来。例如数组A={7, 14, 10, 12, 2, 11, 29, 3, 4},该数组的中点是10,如果令k=5,那么里中点最近的5个数就是{7,14,10,12,11}.

要求对任意一个数组,设计一个时间复杂度为O(n)的算法,该算法能找出距离数组中点最近的K个元素。

这道题的难度是比较大的,在面试中出现的概率应该不大,如果你能在一小时内把它解决并给出代码,那么你的水平在BAT中当个技术经理以上的职位应该是没有太大问题的。

解决这个问题要分两步走:
1, 找到一个算法,能够让我们在O(n)的时间内查找到数组的中点。

2, 假设我们找到了中点m, 那么我们把计算数组前k个元素与中点m的距离,也就是 d(i) = |A[i] - m|, 把这k个元素与中点的距离构建一个大堆。接着在剩下的n - k 个元素中,我们逐次计算他们与中点m的距离,然后用这个距离与大堆中的最大值相比较,如果这个距离大过大堆返回的最大值,那么就忽略这个元素,如果它的距离比大堆的最大值小,那么就把大堆中的最大值去掉,将当前元素加入大堆。

当完成步骤2之后,大堆中的k个元素就是距离终点最近的K个元素。在第二步中,我们需要把元素加入一个大堆,对于一个含有k个元素的大堆而言,加入一个元素的复杂度是O(lgk),所以执行第二步所需的时间复杂度是O(n * lgk)。 由于k是一个常数,所以第二步相对于变量n来说,也是线性的。

现在问题在于第一步的实现,也就是如何通过线性时间的算法找到一个数组的中点,事实上,只要找到第一步的算法,那么整个问题就可以解决了,本质上第二步是多余的,所以本文的重点将在对一步的分析上。我们看看如何设计一个算法,使得我们能在O(n)的时间复杂度内,在数组中找到任意第k大的元素。显然,数组中点是数组中第n/2大的元素,于是要解决问题,我们只要找到第(n/2-k/2)大的元素,然后再找到(n/2+k/2)大的元素,最后查找所有处于这两者之间的元素,那么问题就解决了。

我们看看如何在O(n)的时间内,找到数组中第i大的元素,它的算法步骤如下:
1, 把n个元素分成若干个小组,每个小组有5个元素,于是总共能分成n/5组
2, 对每个小组中的五个元素进行排序,然后取出他们的中点。
3, 利用本算法递归的去查找步骤2中所有中点组成的集合中的中点,假定得到的中点为x。
4, 根据步骤4得到的x把数组划分为两部分,把所有小于x的元素放到x的左边,把所有大于x的元素放到x的右边。假设x的左边有k-1个元素,那么x就是第k小的元素,在x的右边就有n-k个元素。
5, 如果i == k, 那么直接返回x, 如果 i < k, 那么我们在x左边元素集合中,递归的使用该算法去查找第i 小的元素。如果i > k, 那么我们在x的右边元素集合中,使用该算法去查找第(i - k)小的元素。

该算法的实现代码如下:

int select(int[] A, int i) {
       //在数组中查找第i小的元素
      if (A.length == 1) {
          return A[0];
      }
       
       /*
        * 步骤1,将数组分成小组,每个小组含有5个元素,最后一组很可能没有5个元素
        */
       ArrayList<int[]> arrCollection = new ArrayList<int[]>();
       int p = 0;
       int cnt = 5;
       int[] arr = null;
       
       while (p < A.length) {
           if (cnt >= 4) {
               int len = Math.min(5, A.length - p);
               arr = new int[len];
               cnt = 0;
               arrCollection.add(arr);
           } else {
               cnt++;
           }
           
           arr[cnt] = A[p];
           p++;
       }
       
       /*
        * 步骤2,把每个小组中的元素排序,并取出每个小组中的中点
        */
       int[] medians = new int[arrCollection.size()];
       for (int j  = 0; j < arrCollection.size(); j++) {
           Arrays.sort(arrCollection.get(j));
           arr = (int[])arrCollection.get(j);
           medians[j] = arr[arr.length / 2];
       }
       
       /*
        * 步骤3,递归的去获取中点集合中的中点
        */
       int x = select(medians, medians.length/2);
       
       /*
        * 步骤4,把小于x的元素放到x左边,把大于x的元素放到x的右边
        */
       int[] B = new int[A.length]; 
       
       int begin = 0, end = A.length - 1;
       int pos = 0;
       while (pos < A.length) {
           if (A[pos] < x) {
               B[begin] = A[pos];
               begin++;
           }
           
           if (A[pos] > x) {
               B[end] = A[pos];
               end--;
           }
           
           pos++;
       }
       
       B[begin] = x;
       A = B;
       
       
       /*
        * 执行步骤5,如果x左边的元素是k-1个,同时i == k, 那么返回x
        * 如果i < k, 那么递归的去在左边k-1个元素中查找第i小的元素
        * 如果i > k, 那么递归的在右边n-k个元素中,查找第(i - k)小的元素
        */
       if (i == begin) {
           return x;
       } else if (i < begin) {
           arr = Arrays.copyOf(A, begin);
           return select(arr, i);
       } else {
           arr = Arrays.copyOfRange(A, begin+1, A.length);
           return select(arr, i - begin - 1);
       }
       
   }

我们简单对算法进行分析一下:
第一步是把元素分成若干个小组,一次循环就可以完成,所以复杂度是O(n);

第二步是找到每个小组中的中点,由于每个小组只有5个元素,所以第二步的时间复杂度也是O(n);

第三步是在所有中点的集合中再找出中点,这一步涉及到算法的递归,所以我们暂时不做分析。

第四步是把数组元素分成两部分,所需时间复杂度也是O(n)。

第五步是在数组中的某一个子集中再次递归的运行算法。

我们看看执行第3步后,也就是取到中点的中点,假设中点的中点为x,然后我们会把数组元素根据x分成两部分:


这里写图片描述

我们看上图,假设中间点x是我们找到的中点集合中的中点,这样右下角阴影部分的节点,他们的值肯定是大于x的。因为我们把所有节点分成若干个小组,每个小组有5个点,最后一个小组有可能不足5个元素,所有去掉包含x的那个小组,以及最后一个小组,那么就有 (1/2 * n/5) 个小组中,下半部的三个节点都会大于x.于是大于x的元素的个数就至少有:

3* [ (1/2 *n/5) - 2] = 3n/10 - 6 个。

如果我们要找的元素处于前半部分,那么前半部分的元素个数最多有 n - (3n/10 - 6) = 7n/10 + 6 个,如果处于后半部分,那么要处理的元素就有3n/10 - 6 个,也就是说,当我们执行第5步的时候,递归处理的元素个数最多不超过7n/10 + 6个。

我们不深入更加细致的复杂度分析,可以保证的是,算法的最终复杂度是O(n), 也就是说依靠上面算法实现,我们可以在线性时间内从数组中查找处于任意位置的元素。

根据给定的数组A = {7, 14, 10, 12, 2, 11, 29, 3, 4}, 它排序后为:{2, 3, 4, 7, 10, 11, 12, 14, 29},于是第0小的元素是2,第8小的元素是29.如果运行代码:
int y = select(A, 8);

那么我们得到的y值就是29。对代码更详细的讲解和调试演示,请参看视频:

如何进入google,算法面试技能全面提升指南

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:


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

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,724评论 0 33
  • 算法和数据结构 [TOC] 算法 函数的增长 渐近记号 用来描述算法渐近运行时间的记号,根据定义域为自然数集$N=...
    wxainn阅读 1,056评论 0 0
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,164评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,727评论 0 15
  • 自从爱上跑步,去哪里都会带上跑鞋,旅行的时候首先想到的就是带跑鞋,去到新的地方,新的城市,一定要跑步。一边跑步,一...
    Joyce姐姐阅读 924评论 4 9