面試算法:快速獲取數組中點的相鄰區域點

假設給定一個數組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閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容

  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,764評論 0 33
  • 算法和數據結構 [TOC] 算法 函數的增長 漸近記號 用來描述算法漸近運行時間的記號,根據定義域為自然數集$N=...
    wxainn閱讀 1,077評論 0 0
  • 概述 排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    蟻前閱讀 5,211評論 0 52
  • 概述:排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,740評論 0 15
  • 自從愛上跑步,去哪里都會帶上跑鞋,旅行的時候首先想到的就是帶跑鞋,去到新的地方,新的城市,一定要跑步。一邊跑步,一...
    Joyce姐姐閱讀 979評論 4 10