毎日のアルゴリズム: 上位 K 個の高頻度要素

毎日のアルゴリズム: 上位 K 個の高頻度要素

空でない整数の配列が与えられた場合、最も頻繁に出現する上位 k 個の要素を返します。

例1:

  1. 入力: nums = [1,1,1,2,2,3]、k = 2
  2. 出力: [1,2]

例2:

  1. 入力: nums = [1]、k = 1
  2. 出力: [1]

ヒント:

  • 与えられた k は常に妥当であり、1 ≤ k ≤ 配列内の一意の要素の数であると想定できます。
  • アルゴリズムの時間計算量は O(nlogn) より小さくなければなりません。ここで n は配列のサイズです。
  • 質問データは、回答が一意であることを保証します。つまり、配列の最初の k 個の高頻度要素のセットは一意です。
  • 回答は任意の順序で返すことができます。

解決策1: マップ+配列

マップを使用して各要素の頻度を記録し、配列を使用して要素を比較および並べ替えます。

コード実装:

  1. topKFrequent =関数(nums, k) {
  2. map = new Map(), arr = [...新しいSet (nums)] とします。
  3. nums.map((num) => {
  4. map.has(num) の場合、 map.set (num, map.get(num)+1) を実行します。
  5. それ以外の場合はmap.set (num, 1)
  6. })
  7. arr.sort((a, b) => map.get(b) - map.get(a)).slice(0, k);を返します
  8. };

複雑性分析:

  • 時間計算量: O(nlogn)
  • 空間計算量: O(n)

質問では、アルゴリズムの時間計算量が O(n log n) より優れている必要があるため、この実装は質問の要件を満たしていません。

解決策 2: マップ + 小さなトップヒープ

配列を走査して各要素の頻度をカウントし、要素の値(キー)と出現頻度(値)をマップに保存します。

マップデータを通じて、上位 k 個の高頻度要素の小さなトップヒープが構築されます。小さなトップヒープ上の任意のノードの値は、その左と右の子ノードの値以下である必要があります。つまり、ヒープの上部が最小値です。

具体的な手順は次のとおりです。

  • データを走査し、各要素の頻度をカウントし、要素の値(キー)と出現頻度(値)をマップに保存します。
  • マップを横断し、最初のk個の数字で小さなトップヒープを構築します。
  • 位置 k から始めて、マップのトラバースを続けます。各データの発生頻度は、小さなトップ ヒープの最上位要素の発生頻度と比較されます。最上位要素より小さい場合は、処理は行われず、次の要素へのトラバースが続行されます。最上位要素より大きい場合は、この要素がヒープの最上位要素を置き換え、ヒープが小さなトップ ヒープに変換されます。
  • トラバーサルが完了すると、ヒープ内のデータは上位k個の最大データになります。

コード実装:

  1. topKFrequent =関数(nums, k) {
  2. map = new Map()、heap = [,] とします。
  3. nums.map((num) => {
  4. map.has(num) の場合、 map.set (num, map.get(num)+1) を実行します。
  5. それ以外の場合はmap.set (num, 1)
  6. })
  7. // 要素数がk以下の場合
  8. マップのサイズがk以下の場合
  9. [...map.keys()]を返す
  10. }
  11. // 要素数が k より大きい場合は、マップをトラバースして小さなトップヒープを構築します
  12. i = 0とする
  13. map.forEach((値、キー) => {
  14. i < k の場合 {
  15. // 最初の k を取得してヒープを構築し、それをヒープに挿入します
  16. heap.push(キー)
  17. // 最初の k 個のヒープをその場で作成します
  18. if(i === k-1) ヒープを構築(ヒープ、マップ、k)
  19. }そうでない場合、map.get(heap[1]) < 値) {
  20. // 置き換えてスタックする
  21. ヒープ[1] =キー
  22. // 最初の要素を上から下に積み重ねる
  23. ヒープ化(ヒープ, マップ, k, 1)
  24. }
  25. 私は++
  26. })
  27. // ヒープの最初の要素を削除します
  28. ヒープ.shift()
  29. ヒープを返す
  30. };
  31. // 後ろから前へ、上から下へ、その場でヒープを構築します
  32. buildHeap = (ヒープ、マップ、k) => {
  33. if(k === 1)戻り値
  34. // 最後の非リーフノードから始めて上から下に積み重ねます
  35. (i = Math.floor(k/2); i>=1; i --)の場合{
  36. ヒープ化(ヒープ, マップ, k, i)
  37. }
  38. }
  39. // ヒープ
  40. ヒープ化 = (ヒープ、マップ、k、i) => {
  41. // トップダウンスタッキング
  42. while( true ) {
  43. minIndex = i とします
  44. 2*i <= k && map.get(heap[2*i]) < map.get(heap[i])) {
  45. 最小インデックス = 2*i
  46. }
  47. if(2*i+1 <= k && map.get(heap[2*i+1]) < map.get(heap[minIndex])) {
  48. 最小インデックス = 2*i+1
  49. }
  50. 最小インデックスが i の場合
  51. swap(ヒープ, i, minIndex)
  52. i = 最小インデックス
  53. }それ以外{
  54. 壊す
  55. }
  56. }
  57. }
  58. // 交換
  59. swap = (arr, i, j) => {とします。
  60. temp = arr[i]とします。
  61. arr[i] = arr[j]
  62. arr[j] =一時
  63. }

複雑性分析:

  • 時間計算量: 配列の走査には O(n) の時間計算量が必要で、ヒープ化には O(logk) の時間計算量が必要であるため、ヒープを使用して Top k 問題を見つける時間計算量は O(nlogk) です。
  • 空間計算量: O(n)

解決策3: バケットソート

ここでは、最初のk個の高頻度要素を取得する必要があり、カウンティングソートはもはや適していません。前の質問では、カウンティングソートを使用して、要素iがbucket[i]に出現した回数を保存しましたが、この保存ではバケット配列上の値が順番になっていることを保証できません。たとえば、bucket = [0,3,1,2]、つまり要素0は出現せず、要素1は3回出現し、要素2は1回出現し、要素3は2回出現します。したがって、カウンティングソートは最初のk個の高頻度要素を取得するのに適していません。ただし、カウンティングソートが機能しない場合は、バケットソートがあるので心配しないでください。

バケットソートは、カウントソートのアップグレード版です。また、関数のマッピング関係も活用します。

バケット ソートは、入力データが均一に分散されていると想定し、データを有限数のバケットに分割し、各バケットを個別にソートすることによって機能します (別のソート アルゴリズムを使用することも、バケット ソートを再帰的に使用し続けることもできます)。

  • まずマップを使用して頻度を保存します
  • 次に、配列(特定の数のバケットを持つ)を作成し、頻度を配列の添え字として使用して、異なる頻度を持つ数値セットを対応する配列の添え字(バケット内)に格納します。

コード実装:

  1. topKFrequent =関数(nums, k) {
  2. map = new Map(), arr = [...新しいSet (nums)] とします。
  3. nums.map((num) => {
  4. map.has(num) の場合、 map.set (num, map.get(num)+1) を実行します。
  5. それ以外の場合はmap.set (num, 1)
  6. })
  7. // 要素数がk以下の場合
  8. マップのサイズがk以下の場合
  9. [...map.keys()]を返す
  10. }
  11. バケットソート(map, k)を返す
  12. };
  13. // バケットソート
  14. バケットソート = (map, k) => {
  15. arr = []、res = []とします。
  16. map.forEach((値、キー) => {
  17. // マッピング関係(出現頻度を添え字とする)を使用して、各バケットにデータを分配します
  18. if(!arr[値]) {
  19. arr[値] = [キー]
  20. }それ以外{
  21. arr[値].push(キー)
  22. }
  23. })
  24. // 逆順に走査して、最も頻度の高い最初の k 個の数値を取得します。
  25. (i = arr.length - 1;i >= 0 && res.length < k;i --) {
  26. if(arr[i]) {
  27. res.push(...arr[i])
  28. }
  29. }
  30. 戻り
  31. }

複雑性分析:

  • 時間計算量: O(n)
  • 空間計算量: O(n)

リートコード: https://leetcode-cn.com/problems/top-k-frequent-elements/solution/javascript-qian-k-ge-gao-pin-yuan-su-by-user7746o/

<<:  あなたのビジネスに必要な AI 処理ユニットはどれですか?

>>:  フロントエンドではアルゴリズムを理解する必要はないと思いますか?実際の例を見てみましょう。

ブログ    
ブログ    
ブログ    

推薦する

...

...

シリコンチップ上に15万量子ビット:単一スピンの初の光学検出がNature誌に掲載

量子コンピュータは、従来のコンピュータでは解決に数十億年かかる問題を理論的に解決できますが、十分な量...

人工知能は労働力不足の重要な解決策とみられる

セリディアンは、無限の労働力を動員する力に焦点を当てた年次経営者調査の結果を発表しました。調査では、...

...

スポーツと人工知能が出会うとき(スポーツレビュー)

技術開発を積極的に受け入れ、人工知能がスポーツにさらに貢献できるようにしましょう。スポーツとテクノロ...

ディープラーニングのこれらの概念をすべて理解できましたか? TF、TLT、TRT、DS....

最近、NVIDIA GPU 製品や SDK を使用してディープラーニングを学習している学生に多く出会...

自動車ドメインコントローラの統合アーキテクチャの背景、利点、設計を1つの記事で理解する

車両の電動化が徐々に進むにつれ、電子制御ユニット(ECU)が車全体を制御するようになりました。アンチ...

労働者は大きなモデルに遭遇します。外の世界はすでにこのように機能しているのでしょうか?

オフィスのシナリオでは、PPT の作成は最も一般的なタスクの 1 つです。業務報告、製品発表、イベン...

1000ステップ未満の微調整で、LLaMAコンテキストは32Kに拡張されました。これは、Tian Yuandongチームの最新の研究です。

誰もが独自の大規模モデルをアップグレードして反復し続けるにつれて、コンテキスト ウィンドウを処理する...

倪光南:AI開発は教訓を学ぶべき、コア技術は購入したり置き換えたりすることはできない

「ここ数年、情報技術分野で私たちが学んだ最大の教訓の一つは、主要な中核技術は私たち自身の独立したイノ...

機械学習プロジェクトにおける特徴エンジニアリングの 5 つのベスト プラクティス

私たちは長年にわたり、機械学習プロジェクトで何が機能し、何が機能しないかを特定するために、さまざまな...

...

著者の半数以上が中国人です! Google Researchの画像表現モデルALIGNがImageNetを支配

[[399343]]ニューラル ネットワークは実際には表現を学習しています。CV の分野では、優れ...

...