毎日のアルゴリズム: 上位 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 処理ユニットはどれですか?

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

ブログ    
ブログ    

推薦する

Google が基本世界モデルをリリース: 110 億のパラメータ、インタラクティブな仮想世界を生成可能

Sora がリリースされてからまだ 2 週間も経っていないが、Google の世界モデルが登場し、そ...

...

AI声優が偽の声を本物らしくする方法

AI音声スタートアップ企業のソナンティックは、オーディオディープフェイクで小さな進歩を遂げ、からかっ...

...

Linux 仮想化ガイド: 仮想化環境の構築

仮想化技術はコンピューティング分野で幅広い用途があり、ハードウェア リソースの利用率を向上させ、メン...

少数ショット学習における SetFit によるテキスト分類

翻訳者 |陳俊レビュー | Chonglouこの記事では、「少量学習」の概念を紹介し、テキスト分類で...

AGVロボットマルチエージェント経路探索の4つの主要な研究方向

マルチエージェント経路探索 (MAPF) は、人工知能、ロボット工学、理論計算機科学、実践的オペレー...

モデルは、人々の言葉をいくつか聞くことで、よりよく学習できるでしょうか?スタンフォード大学は学習を支援するために言語説明を使うことを提案している

言語は人々の間で最も自然なコミュニケーションの方法であり、多くの重要な情報を伝達するのに役立ちます。...

...

LLaVA: GPT-4V(ision) のオープンソース代替品

LLaVA (Large Language and Vision Assistant) は、画像翻訳...

人工知能学習: 人工ニューラル ネットワークとは何ですか?

[51CTO.com クイック翻訳] 多くの人工知能コンピュータシステムの中核技術は、人間の脳の生...

大規模なモデルをグローバルに微調整できないわけではなく、LoRA の方がコスト効率が高いだけです。チュートリアルは準備完了です。

データ量とモデルパラメータの数を増やすことが、ニューラル ネットワークのパフォーマンスを向上させる最...

機械学習が自閉症の「非コード変異」の秘密を解明

新たな研究によると、遺伝子間の自然発生的な突然変異は、生まれつきの遺伝子と同じくらい自閉症において重...

...

ディープラーニングを始めるために理解すべき25の概念

[[245072]] 1. ニューロン- 脳の基本要素を形成するニューロンと同様に、ニューロンはニュ...