毎日のアルゴリズム: データストリームの中央値

毎日のアルゴリズム: データストリームの中央値

[[431427]]

この記事はWeChatの公開アカウント「3分でフロントエンドを学ぶ」から転載したもので、著者はsisterAnです。この記事を転載する場合は、「3分で学ぶフロントエンド」公式アカウントまでご連絡ください。

中央値は、順序付けられたリスト内の中央の数値です。リストの長さが偶数の場合、中央値は中央の 2 つの数値の平均になります。

例えば、

[2,3,4]の中央値は3

[2,3]の中央値は(2 + 3) / 2 = 2.5である。

次の 2 つの操作をサポートするデータ構造を設計します。

  • void addNum(int num) - データ ストリームからデータ構造に整数を追加します。
  • double findMedian() – これまでのすべての要素の中央値を返します。

例:

  1. 追加番号(1)
  2. 追加番号(2)
  3. 中央値を見つける() -> 1.5
  4. 追加番号(3)
  5. 中央値を見つける() -> 2

高度な:

  • データ ストリーム内のすべての整数が 0 ~ 100 の範囲内にある場合、アルゴリズムをどのように最適化しますか?
  • データ ストリーム内の整数の 99% が 0 ~ 100 の範囲内にある場合、アルゴリズムをどのように最適化しますか?

この動的配列の中央値問題を見ても、興奮しすぎないでください。これはヒープを使用する完璧な例です。ヒープの典型的な応用である中央値問題をテストします。詳細については、「Advanced Front-end Algorithms 9」を参照してください。この記事を読めば、ヒープ ソート、Top K、中央値問題に関する面接を恐れることはなくなります。

解決策: ヒープの使用

解決:

維持するヒープは 2 つあります。

  • 最大ヒープ: 最初の n/2 個の小さな要素にアクセスするために使用されます。n が奇数の場合、最初の Math.floor(n/2) + 1 個の要素にアクセスするために使用されます。
  • 小さなトップヒープ: 最後のn/2個の小さな要素を保存およびアクセスするために使用されます。

すると、質問によれば、中央値は次のようになります。

  • nは奇数です: 中央値は最大ヒープの最上位要素です
  • nは偶数です。中央値は、大きなヒープの最上位要素と小さなヒープの最上位要素の平均です。

配列が動的である場合、配列に要素を挿入するたびにヒープをどのように調整しますか?

挿入された要素が最大ヒープの上限より大きい場合、その要素は最小ヒープ内に挿入され、小さい場合は最大ヒープ内に挿入されます。

挿入が完了した後、ビッグトップヒープまたはスモールトップヒープ内の要素数が上記の要件を満たさない場合は、要件が満たされるまで、ビッグトップヒープの最上位要素またはスモールトップヒープの最上位要素を別のヒープへ継続的に移動する必要があります。

コード実装:

  1. MedianFinder =関数() {
  2. // 最初の n/2 個の最小要素を格納するための大きなヒープ
  3. this.lowHeap = 新しい MaxHeap()
  4. // 小さなトップヒープ。次の n/2 個の最小要素を格納するために使用します。
  5. this.hightHeap = 新しい MinHeap()
  6. };
  7. // 要素を挿入
  8. MedianFinder.prototype.addNum =関数(数値) {
  9. // 最大ヒープが空の場合、または最大ヒープの先頭の要素がnumより小さい場合は、それを最大ヒープに挿入します
  10. // それ以外の場合は、小さなトップヒープに挿入します
  11. if(!this.lowHeap.getSize() || num < this.lowHeap.getHead()) {
  12. // 最大ヒープの上限より小さい場合は、最大ヒープに挿入します
  13. this.lowHeap.insert (数値)
  14. }それ以外{
  15. // 小さなヒープの先頭より大きい場合は、小さなヒープに挿入します
  16. this.hightHeap.insert (数値)
  17. }
  18.  
  19. // 最上位ヒープのサイズを比較して、まだバランスが取れているかどうかを確認します
  20. this.lowHeap.getSize() が this.hightHeap.getSize() より大きい場合、
  21. // 大きなトップヒープから小さなトップヒープに移行する
  22. this.hightHeap.insert ( this.lowHeap.removeHead ())
  23. }
  24. this.hightHeap.getSize() が this.lowHeap.getSize() より大きい場合、
  25. // 小さなトップヒープから大きなトップヒープに移行する
  26. this.lowHeap.insert (this.hightHeap.removeHead())
  27. }
  28. };
  29. // 中央値を取得する
  30. MedianFinder.prototype.findMedian =関数() {
  31. this.lowHeap.getSize() と this.lowHeap.getSize() が等しい場合、 this.hightHeap.getSize() が返されます。
  32. (this.lowHeap.getHead() + this.hightHeap.getHead())/2を返します
  33. }
  34. this.lowHeap.getHead()を返す
  35. };

小さなトップヒープの定義は次のとおりです。

  1. // 小さなトップヒープ
  2. MinHeap =関数() {
  3. ヒープ = [,] とする
  4. // ヒープ内の要素数
  5. this.getSize = () => ヒープの長さ - 1
  6. // 入れる
  7. this.insert = (キー) => {
  8. heap.push(キー)
  9. // 保存場所を取得する
  10. i = ヒープの長さ - 1 とします。
  11. (Math.floor(i/2) > 0 && heap[i] < heap[Math.floor(i/2)]) の場合 {
  12. swap(heap, i, Math.floor(i/2)); // スワップ
  13. i = Math.floor(i/2);
  14. }
  15. }
  16. // ヒープヘッダーを削除して戻ります
  17. this.removeHead = () => {
  18. ヒープの長さが1より大きい場合
  19. (heap.length === 2)の場合、heap.pop()を返します
  20. num = ヒープ[1]とする
  21. ヒープ[1] = ヒープ.pop()
  22. ヒープ化(1)
  23. 数値を返す
  24. }
  25. 戻る ヌル 
  26. }
  27. // 山の先頭を取得する
  28. this.getHead = () => {
  29. heap.length > 1を返すか、heap[1]: null  
  30. }
  31. // ヒープ
  32. ヒープ化 = (i) => {
  33. k = ヒープの長さ - 1 とします。
  34. // トップダウンスタッキング
  35. while( true ) {
  36. minIndex = i とします
  37. 2*i <= k && ヒープ[2*i] < ヒープ[i]) {
  38. 最小インデックス = 2*i
  39. }
  40. 2*i+1 <= k && ヒープ[2*i+1] < ヒープ[最小インデックス]) {
  41. 最小インデックス = 2*i+1
  42. }
  43. 最小インデックスが i の場合
  44. swap(ヒープ, i, minIndex)
  45. i = 最小インデックス
  46. }それ以外{
  47. 壊す
  48. }
  49. }
  50. }
  51. swap = (arr, i, j) => {とします。
  52. temp = arr[i]とします。
  53. arr[i] = arr[j]
  54. arr[j] =一時 
  55. }
  56. }

最大ヒープの定義は次のとおりです。

  1. // ビッグトップヒープ
  2. MaxHeap =関数() {
  3. ヒープ = [,] とする
  4. // ヒープ内の要素数
  5. this.getSize = ()=>ヒープの長さ - 1
  6. //最大ヒープに挿入
  7. this.insert = (キー) => {
  8. heap.push(キー)
  9. // 保存場所を取得する
  10. i = ヒープの長さ - 1 とします。
  11. (Math.floor(i/2) > 0 && heap[i] > heap[Math.floor(i/2)]) の場合 {
  12. swap(heap, i, Math.floor(i/2)); // スワップ
  13. i = Math.floor(i/2);
  14. }
  15. }
  16. // 山の先頭を取得する
  17. this.getHead = () => {
  18. heap.length > 1を返すか、heap[1]: null  
  19. }
  20. // ヒープヘッダーを削除して戻ります
  21. this.removeHead = () => {
  22. ヒープの長さが1より大きい場合
  23. (heap.length === 2)の場合、heap.pop()を返します
  24. num = ヒープ[1]とする
  25. ヒープ[1] = ヒープ.pop()
  26. ヒープ化(1)
  27. 数値を返す
  28. }
  29. 戻る ヌル 
  30. }
  31. // ヒープ
  32. ヒープ化 = (i) => {
  33. k = ヒープの長さ - 1 とします。
  34. // トップダウンスタッキング
  35. while( true ) {
  36. maxIndex = iとする
  37. 2*i <= k && ヒープ[2*i] > ヒープ[i]) {
  38. 最大インデックス = 2*i
  39. }
  40. 2*i+1 <= k && ヒープ[2*i+1] > ヒープ[maxIndex]) {
  41. 最大インデックス = 2*i+1
  42. }
  43. if(maxIndex !== i) {
  44. swap(ヒープ, i, 最大インデックス)
  45. i = 最大インデックス
  46. }それ以外{
  47. 壊す
  48. }
  49. }
  50. }
  51. swap = (arr, i, j) => {とします。
  52. temp = arr[i]とします。
  53. arr[i] = arr[j]
  54. arr[j] =一時 
  55. }
  56. }

複雑性分析:

時間計算量: ヒープに要素を挿入する時間計算量は O(logn) で、これはツリーの高さです。ヒープの最上位要素を移動するにはヒープ化も必要なので、時間計算量も O(logn) です。したがって、挿入 ( addNum ) の時間計算量は O(logn) であり、各挿入後に中央値を見つけるにはヒープの最上位要素を返すだけでよく、 findMedian の時間計算量は O(1) です。

空間計算量: O(n)

データストリーム内のすべての整数が0から100の範囲にある場合、カウントソートを使用することができますが、カウントソートの時間計算量はO(n + m)であり、ここでmはデータ範囲を表し、計算量が高いため、ここでは適していません。カウントソートは、静的配列の上位k値に適しています。Leetcode347:上位K高頻度要素

リートコード: https://leetcode-cn.com/problems/find-median-from-data-stream/solution/javascriptshu-ju-liu-de-zhong-wei-shu-by-user7746o/

<<:  機械学習がサプライチェーン管理を変える10の方法

>>:  将来、ロボットが私たちを支配するようになるのでしょうか?

推薦する

誰もがエンドツーエンドに取り組んでいますが、エンドツーエンドの自動運転の基礎は何でしょうか?

この記事は、Heart of Autonomous Driving の公開アカウントから許可を得て転...

2019年のAI技術のブレークスルーをすべて見る

この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...

...

今後5年間の15の主要なテクノロジートレンド

私たちの生活、仕事、交流の仕方に革命をもたらす技術の進歩によって、未来は常に形を変えています。今後 ...

TensorFlow で発見された脆弱性の背後にあるもの: AI セキュリティに関する私たちの愚かさと無知

AI がインターネット セキュリティに与える影響について議論してきたとき、AI 自体も安全ではないと...

百度の張亜琴社長:AIは現代の最も変革的な力である

[[205882]]北京時間10月10日朝のニュースによると、中国の検索大手、百度はシアトル地域にオ...

ディープラーニングの成果は収穫されようとしているのでしょうか? 11人の専門家がAIの現在(2018年)と未来(2019年)について語る

KDnuggets は、学界と産業界のさまざまな分野の機械学習と AI の専門家 11 名に相談し、...

Google の Bard チャットボットがアップデートされ、リアルタイムで応答を生成できるようになりました

10 月 29 日現在、大規模言語モデル (LLM) では即座に回答を出すことができないため、質問を...

人工知能やモノのインターネットから仮想現実やブロックチェーンまで、将来の技術進歩の大部分はクラウドで起こるだろう。

今では、ほとんどの企業リーダーがクラウド コンピューティングの価値を理解しています。すでに多くの人が...

ドローンの出現と市場の需要の変化

ドローンの市場、入手可能性、需要が長年にわたってどのように増加してきたかを学びます。映画の架空の世界...

...

5G+AI: 未来に影響を与える新たなトレンド

7月9日、2020年世界人工知能会議クラウドサミットが正式に開幕しました。 AI という SF 用語...

フロントエンドでも機械学習を理解する必要がある

[[374893]]背景:近年、機械学習の人気は高まり続けており、フロントエンド分野も継続的に展開さ...