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

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

[[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の方法

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

ブログ    
ブログ    
ブログ    

推薦する

...

文書翻訳における人工知能: 効率化の新時代

今日、言語を超えた効果的なコミュニケーションはこれまで以上に重要になっています。企業が新しい市場に進...

297 件の論文すべてを 1 つの記事で読むことができます。中国科学院が「拡散モデルに基づく画像編集」に関する初のレビューの出版を主導

この記事では、画像編集の最先端の手法を包括的に研究し、技術的なルートに基づいて 3 つの主要なカテゴ...

機械学習における勾配降下法

最大化問題は、機械学習アルゴリズムの非常に重要な部分です。ほぼすべての機械学習アルゴリズムの中核は、...

米陸軍は航空機、戦車、VR訓練にデジタルツインプロジェクトを導入している

将来のサプライチェーンにおける 3D プリント技術の潜在的な役割を判断するために、米国陸軍は UH-...

マルチモーダル生成AIの深掘り

マルチモーダル生成型人工知能 (GenAI) は、汎用人工知能の実現に向けた次の大きな進歩と言えます...

人工知能を活用して機密情報を安全に保つ 5 つの方法

人工知能は企業や消費者にとって非常に便利なツールですが、この技術をどのように活用して機密情報を保護で...

人工知能はサイバーセキュリティにどのような影響を与えるのでしょうか?

人工知能の出現はITの将来の発展の傾向を変え、今後もさらに多くの産業に利益をもたらし続けるでしょう。...

...

...

Pythonを知らない人は、人工知能時代の新たな「文盲」になるだろう

各段階で、「文盲」の定義は異なります。以前は、漢字を知らないことが文盲とみなされ、後には、英語を話せ...

未来の戦場は「瞬殺」の時代へ、人工知能が威力を発揮

近年、人工知能技術は飛躍的な進歩を遂げ、戦闘指揮の分野で広く応用され、観察・判断・決定・行動(OOD...

アルゴリズムの問​​題を解決するための Python 3 コード フレームワーク

序文現在インターンシップをしており、仕事量はそれほど多くないので、空き時間を利用してPATのウェブサ...