From 6e8e7b8d1dc33d857590c1a3fd514ea3a042cb9d Mon Sep 17 00:00:00 2001 From: Yusuke Katsuki Date: Tue, 29 Apr 2025 19:57:11 -0400 Subject: [PATCH 1/4] Step1 --- 0347_Top_K_Frequent_Elements/solution_ja.md | 41 ++++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/0347_Top_K_Frequent_Elements/solution_ja.md b/0347_Top_K_Frequent_Elements/solution_ja.md index 374e0e1..a9d9600 100644 --- a/0347_Top_K_Frequent_Elements/solution_ja.md +++ b/0347_Top_K_Frequent_Elements/solution_ja.md @@ -1,20 +1,48 @@ ## Problem + // The URL of the problem ## Step 1 -5分程度答えを見ずに考えて、手が止まるまでやってみる。 + +5 分程度答えを見ずに考えて、手が止まるまでやってみる。 何も思いつかなければ、答えを見て解く。ただし、コードを書くときは答えを見ないこと。 動かないコードも記録する。 -正解したら一旦OK。思考過程もメモする。 +正解したら一旦 OK。思考過程もメモする。 -### Approach -* +### Approach 1. Map と max-heap を使った解法 -```java +- 解法自体はすぐ思いついたが、Map での for 文、Max-heaap の書き方が分からず検索した + +- Map に番号と出現回数のペアを保存 +- 空の max-heap を用意し、Map に保存されているペアを出現回数を軸として挿入 +- Max-heap から k 回 poll()する +```java +class Solution { + public int[] topKFrequent(int[] nums, int k) { + HashMap numCounts = new HashMap<>(); + for (int num : nums) { + numCounts.put(num, numCounts.getOrDefault(num, 0) + 1); + } + PriorityQueue countHeap = new PriorityQueue<>( + (a, b) -> b[1] - a[1] // Max-heap + ); + + numCounts.forEach((num, count) -> { + countHeap.offer(new int[] {num, count}); + }); + + int[] result = new int[k]; + for (int i = 0; i < k; i++) { + result[i] = countHeap.poll()[0]; + } + return result; + } +} ``` ## Step 2 + 他の方が描いたコードを見て、参考にしてコードを書き直してみる。 参考にしたコードのリンクは貼っておく。 読みやすいことを意識する。 @@ -25,8 +53,9 @@ ``` ## Step 3 + 今度は、時間を測りながら、もう一回書く。 -アクセプトされたら消すを3回連続できたら問題はOK。 +アクセプトされたら消すを 3 回連続できたら問題は OK。 ```java From 135ffbe19ef36f981a46aa57ac7d875526c1bd46 Mon Sep 17 00:00:00 2001 From: Yusuke Katsuki Date: Wed, 30 Apr 2025 16:06:51 -0400 Subject: [PATCH 2/4] split to 2 lines --- 0347_Top_K_Frequent_Elements/solution_ja.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/0347_Top_K_Frequent_Elements/solution_ja.md b/0347_Top_K_Frequent_Elements/solution_ja.md index a9d9600..8c834ab 100644 --- a/0347_Top_K_Frequent_Elements/solution_ja.md +++ b/0347_Top_K_Frequent_Elements/solution_ja.md @@ -22,7 +22,8 @@ class Solution { public int[] topKFrequent(int[] nums, int k) { HashMap numCounts = new HashMap<>(); for (int num : nums) { - numCounts.put(num, numCounts.getOrDefault(num, 0) + 1); + int count = numCounts.getOrDefault(num, 0) + 1; + numCounts.put(num, count); } PriorityQueue countHeap = new PriorityQueue<>( (a, b) -> b[1] - a[1] // Max-heap @@ -38,7 +39,6 @@ class Solution { } return result; } -} ``` ## Step 2 From c81960f7e40f5f9a1c867b7005e729808438a15c Mon Sep 17 00:00:00 2001 From: Yusuke Katsuki Date: Fri, 2 May 2025 14:23:14 -0400 Subject: [PATCH 3/4] Step 2 & 3 --- 0347_Top_K_Frequent_Elements/solution_ja.md | 174 +++++++++++++++++++- 1 file changed, 165 insertions(+), 9 deletions(-) diff --git a/0347_Top_K_Frequent_Elements/solution_ja.md b/0347_Top_K_Frequent_Elements/solution_ja.md index 8c834ab..03ee6e1 100644 --- a/0347_Top_K_Frequent_Elements/solution_ja.md +++ b/0347_Top_K_Frequent_Elements/solution_ja.md @@ -1,6 +1,6 @@ ## Problem -// The URL of the problem +https://leetcode.com/problems/top-k-frequent-elements/ ## Step 1 @@ -9,7 +9,10 @@ 動かないコードも記録する。 正解したら一旦 OK。思考過程もメモする。 -### Approach 1. Map と max-heap を使った解法 +### Approach. Map と max-heap を使った解法 + +時間計算量: O(n log k) +空間計算量: O(n) - 解法自体はすぐ思いついたが、Map での for 文、Max-heaap の書き方が分からず検索した @@ -20,25 +23,26 @@ ```java class Solution { public int[] topKFrequent(int[] nums, int k) { - HashMap numCounts = new HashMap<>(); + HashMap numToCount = new HashMap<>(); for (int num : nums) { - int count = numCounts.getOrDefault(num, 0) + 1; - numCounts.put(num, count); + int count = numToCount.getOrDefault(num, 0) + 1; + numToCount.put(num, count); } - PriorityQueue countHeap = new PriorityQueue<>( + PriorityQueue countMaxHeap = new PriorityQueue<>( (a, b) -> b[1] - a[1] // Max-heap ); - numCounts.forEach((num, count) -> { - countHeap.offer(new int[] {num, count}); + numToCount.forEach((num, count) -> { + countMaxHeap.offer(new int[] {num, count}); }); int[] result = new int[k]; for (int i = 0; i < k; i++) { - result[i] = countHeap.poll()[0]; + result[i] = countMaxHeap.poll()[0]; } return result; } +} ``` ## Step 2 @@ -48,8 +52,141 @@ class Solution { 読みやすいことを意識する。 他の解法も考えみる。 +- https://github.com/plushn/SWE-Arai60/pull/9/files#diff-291784abce91292492a5acad677268bfd565410367cfc5e4dc916da5c37556a3R60 + - > heap の代わりに sort を使用してソートしていく。key 関数でソートしたい要素を指定できる。 +- https://github.com/nittoco/leetcode/pull/48/files#diff-013dde8937de27960afd2d09c6325ebf53eb00c65d9d2772bc598e798bcdb877R37 + - > Counter を使う方法がある。特に、most_common を使うと楽 + - Python にある most_common()メソッドは、list から 共通要素とその個数をペアにして tuple の list を返してくれる + - 参考 + - 説明: https://www.geeksforgeeks.org/python-most_common-function/ + - 実装: https://github.com/python/cpython/blob/3d4ac1a2c2b610f35a9e164878d67185e4a3546f/Lib/collections/__init__.py#L625 +- https://github.com/fhiyo/leetcode/pull/12/files#diff-1e3421714509a1abfec6bd79b5deb941602373f6b8c4cdc4c5c129a2a697c800R62 + - > quick select による解法 + - quick select を知らなかった。未整列の配列から k 番目に小さい(大きい)要素を取り出すアルゴリズムらしい。Quick Sort アルゴリズムの一種だが、配列全体をソートする訳ではなく、配列を分割して目的の k 番目の要素を見つけることのみに重点を置く + +### Approach 1. Max-Heap + +- Step1 の改良版。解法は同じ。 +- Step1 では heap に[数値, 回数]ペアを表す int[]を丸ごと入れる、一方で改良版は key(Integer)だけを入れ、heap 作成時の設定で Map を参照する事で頻度を取得している +- 配列でペアを作る方法は暗黙的な使い道の解釈を読み手に強制する点で読み手側の負担が大きいと感じる + ```java +class Solution { + public int[] topKFrequent(int[] nums, int k) { + Map numToCount = new HashMap<>(); + for (int num : nums) { + int count = numToCount.getOrDefault(num, 0) + 1; + numToCount.put(num, count); + } + + PriorityQueue countMaxHeap = new PriorityQueue<>( + (a, b) -> numToCount.get(b) - numToCount.get(a) + ); + countMaxHeap.addAll(numToCount.keySet()); + int[] result = new int[k]; + for (int i = 0; i < k; i++) { + result[i] = countMaxHeap.poll(); + } + return result; + } +} +``` + +### Approach 2. Sort を使った解法 + +時間計算量: O(n log n) +空間計算量: O(n) + +```java +class Solution { + public int[] topKFrequent(int[] nums, int k) { + Map numToCount = new HashMap<>(); + for (int num : nums) { + int count = numToCount.getOrDefault(num, 0) + 1; + numToCount.put(num, count); + } + + List uniqueNums = new ArrayList<>(numToCount.keySet()); + Collections.sort(uniqueNums, (a, b) -> numToCount.get(b) - numToCount.get(a)); + + int[] result = new int[k]; + for (int i = 0; i < k; i++) { + result[i] = uniqueNums.get(i); + } + return result; + } +} +``` + +### Approach 3. Quick Select を使った解法 + +時間計算量: 平均 O(n)、最悪(毎回偏った pivot 選択をした場合) O(n^2) +空間計算量: O(n) + +- まず各数字の出現回数を HashMap に記録する。その後、Map の key(= ユニークな数字の set)を配列に格納する +- Quick Select アルゴリズムを用いて、出現回数に基づいて配列をパーティショニングする + - 任意の pivot 要素 を用意し pivot より大きいグループと小さいグループに配列を分割する +- 上位 k 個の要素がパーティションより右側に来るように繰り返す + +```java +class Solution { + public int[] topKFrequent(int[] nums, int k) { + // Step 1: Count the frequency of each number + Map numToCount = new HashMap<>(); + for (int num : nums) { + int count = numToCount.getOrDefault(num, 0) + 1; + numToCount.put(num, count); + } + + // Step 2: Create a list of unique numbers + List uniqueNums = new ArrayList<>(numToCount.keySet()); + + // Step 3: Use Quick Select to find the k most frequent elements + int left = 0; + int right = uniqueNums.size() - 1; + int targetIndex = uniqueNums.size() - k; + + while (left < right) { + int pivotIndex = partition(uniqueNums, left, right, numToCount); + + if (pivotIndex == targetIndex) { + break; + } else if (pivotIndex < targetIndex) { + left = pivotIndex + 1; + } else { + right = pivotIndex - 1; + } + } + + // Step 4: Return the k most frequent elements + int[] result = new int[k]; + for (int i = 0; i < k; i++) { + result[i] = uniqueNums.get(uniqueNums.size() - 1 - i); + } + + return result; + } + + private int partition(List nums, int left, int right, Map numToCount) { + // Using the rightmost element as pivot + int pivotFrequency = numToCount.get(nums.get(right)); + int i = left; + + for (int j = left; j < right; j++) { + if (numToCount.get(nums.get(j)) <= pivotFrequency) { + // Swap elements + Collections.swap(nums, i, j); + i++; + } + } + + // Swap the pivot to its final position + Collections.swap(nums, i, right); + + return i; + } +} ``` ## Step 3 @@ -58,5 +195,24 @@ class Solution { アクセプトされたら消すを 3 回連続できたら問題は OK。 ```java +class Solution { + public int[] topKFrequent(int[] nums, int k) { + Map numToCount = new HashMap<>(); + for (int num : nums) { + int count = numToCount.getOrDefault(num, 0) + 1; + numToCount.put(num, count); + } + + PriorityQueue countMaxHeap = new PriorityQueue<>( + (a, b) -> numToCount.get(b) - numToCount.get(a) + ); + countMaxHeap.addAll(numToCount.keySet()); + int[] result = new int[k]; + for (int i = 0; i < k; i++) { + result[i] = countMaxHeap.poll(); + } + return result; + } +} ``` From be04a653543bad050091ade760f41a239e206ef8 Mon Sep 17 00:00:00 2001 From: Yusuke Katsuki Date: Sat, 3 May 2025 19:46:38 -0400 Subject: [PATCH 4/4] fixed based on comments --- 0347_Top_K_Frequent_Elements/solution_ja.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/0347_Top_K_Frequent_Elements/solution_ja.md b/0347_Top_K_Frequent_Elements/solution_ja.md index 03ee6e1..8168d89 100644 --- a/0347_Top_K_Frequent_Elements/solution_ja.md +++ b/0347_Top_K_Frequent_Elements/solution_ja.md @@ -128,6 +128,8 @@ class Solution { - Quick Select アルゴリズムを用いて、出現回数に基づいて配列をパーティショニングする - 任意の pivot 要素 を用意し pivot より大きいグループと小さいグループに配列を分割する - 上位 k 個の要素がパーティションより右側に来るように繰り返す + - 探索対象となるサブ配列の開始/終了インデックス(left/right)をループのたびに更新し、上位 k 要素が右半分に集まるまで範囲を狭めていく + - pivotIndex < targetIndex のときは、「右側に上位 k 個がまとまる可能性がある」ので、left = pivotIndex + 1 として探索範囲を右半分に絞る。pivotIndex > targetIndex のときは逆に左半分に絞る ```java class Solution { @@ -189,6 +191,11 @@ class Solution { } ``` +いただいたコメント + +- > 個人的には、i の生存期間はある程度長いし重要な変数なので、もうちょっとしっかりした変数名をつけたい気持ちになります。 + - pivotIndex とか storeIndex とかもう少ししっかりした名前にした方がいいかもと思った + ## Step 3 今度は、時間を測りながら、もう一回書く。