From 0f415b644a855c7ce1a45685e2706fc03adf780e Mon Sep 17 00:00:00 2001 From: busker <165013324+hiroki-horiguchi-dev@users.noreply.github.com> Date: Fri, 20 Mar 2026 18:05:38 +0900 Subject: [PATCH 1/3] Create 307.md --- heap-priorityQueue/307.md | 353 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 353 insertions(+) create mode 100644 heap-priorityQueue/307.md diff --git a/heap-priorityQueue/307.md b/heap-priorityQueue/307.md new file mode 100644 index 0000000..7bb475c --- /dev/null +++ b/heap-priorityQueue/307.md @@ -0,0 +1,353 @@ +# 1st +- 問題: [347. Top K Frequent Elements](https://leetcode.com/problems/top-k-frequent-elements/) +- コメント集: []() +- 過去に解いたことがあるため、1stのみです +- 方針2つ + - bucketソート + - 時間計算量: 定数落としてO(N), 空間計算量: O(N) + - hashmap で nums にて与えられる数字の出現頻度を数える + - map の key,value を逆にして、List の value 番目に key を入れる方針 + - List の value 番目には ArrayList を入れる + - 最後に List を末尾から走査して、k個目まで result に格納したら、result を返す + - PriorityQueue(minHeap) + - 時間計算量: O(n log k). 内訳: add() -> bubble up, poll() -> heapify() が動くため O(log k). これが frequency の n 個に対して動作するため + - 空間計算量: O(N) + - bucketソートと同様 + - map で出現頻度を数える + - key, value のうち、value を使って minHeap を作成する + - 比較にラムダ式を用いて、minHeap or maxHeap を決めないと比較できないところが直感的に理解できなかった + +## 実装-bucketソート +```java +class Solution { + public int[] topKFrequent(int[] nums, int k) { + int[] result = new int[k]; + HashMap frequency = new HashMap<>(); + + for (int num : nums) { + frequency.put(num, frequency.getOrDefault(num, 0) + 1); + } + + List[] bucket = new List[nums.length + 1]; + for (int key : frequency.keySet()) { + int value = frequency.get(key); + if (bucket[value] == null) { + bucket[value] = new ArrayList(); + } + bucket[value].add(key); + } + + int idx = 0; + for (int i = bucket.length - 1; i >= 0 && idx < k; i--) { + List mostFrequent = bucket[i]; + if (mostFrequent != null) { + for (int key : mostFrequent) { + result[idx] = key; + idx++; + if (idx == k) { + break; + } + } + } + } + return result; + } +} +``` + +## minHeap +```java +class Solution { + public int[] topKFrequent(int[] nums, int k) { + int[] result = new int[k]; + + HashMap frequency = new HashMap<>(); + for (int num : nums) { + frequency.put(num, frequency.getOrDefault(num, 0) + 1); + } + + PriorityQueue> minHeap = new PriorityQueue<>((a, b) -> a.getValue() - b.getValue()); + + for (Map.Entry map : frequency.entrySet()) { + minHeap.add(map); + if (minHeap.size() > k) { + minHeap.poll(); + } + } + + for (int i = 0; i < k; i++) { + result[i] = minHeap.poll().getKey(); + } + + return result; + } +} +``` + +## minHeap を何も見ずに実装してみる + +### 1回目 +```java +class MinHeap { + // heap は int[] 配列とする + int[] heap; + // insert, pop, 時にheapの数を管理する + int size = 0; + // 初期容量を覚えておく + int maxSize; + + public MinHeap(int maxSize) { + this.heap = new int[maxSize + 1]; + this.heap[0] = Integer.MIN_VALUE; + this.maxSize = maxSize; + size++; + } + + public int pop() { + if (heap[1] != null) { + int result = heap[1]; + heap[1] = heap[size - 1]; + heap = heap[size - 1]; + heapify(1); + } else { + throw new ArrayIndexOutOfBoundsException("minheap don't have root"); + } + } + + public int peek() { + // heap[1]を返すだけ, 一応nullチェック + if (heap[1] != null) { + return heap[1]; + } else { + throw new ArrayIndexOutOfBoundsException("minheap don't have root"); + } + } + + public void add(int element) { + // size == maxValue となった時、heapのサイズは heap[size++] として拡張する + if (isFull()) { + // size が maxSize+1 と同値になった時 heap[] が格納できるサイズ上限に達したのでサイズを増やす + heap = heap[size + 1]; + } + heap[size] = element; + // minHeap なので、末尾に加える。親と比較して小さいならswapするを、再帰的に行う + // この書き方はちょっと冗長かな。。別関数に切り出した方が良いかも。。 + int index = size; + int parentIndex = parentIndex(index); + while (heap[index] < heap[parentIndex]) { + swap(index, parentIndex); + index = parentIndex; + parentIndex = parentIndex(index); + } + size++; + } + + private boolean isFull() { + return size == maxSize + 1; + } + + private void heapify(int index) { + if (!hasLeftChild(index)) { + return; + } + + int smallestIndex = leftChildIndex(index); + if (heap[rightChildIndex(index)] < heap[leftChildIndex(index)]) { + smallestIndex = rightChildIndex(index); + } + if (heap[index] < heap[smallestIndex]) { + swap(index, smallestIndex); + heapify(smallestIndex); + } + } + + private int parentIndex(int index) { + return index / 2; + } + + private int leftChildIndex(int index) { + return index * 2; + } + + private int rightChildIndex(int index) { + return index * 2 + 1; + } + + private boolean hasLeftChild(int index) { + return heap[leftChildIndex(index)] != null; + } + + private void swap(int i, int j) { + int temp = heap[i]; + heap[i] = heap[j]; + heap[j] = temp; + } +} +``` + +### Claude に以下レビューを受けたので2回目で修正 +① size++ をコンストラクタに入れている +番兵は要素ではないので size は 0 のままでいいです。 + +```java +public MinHeap(int maxSize) { +this.heap = new int[maxSize + 1]; +this.heap[0] = Integer.MIN_VALUE; +this.maxSize = maxSize; +// size++ は不要 +} +``` + +② int に null チェックはできない +先週も出てきましたね。int はプリミティブなので != null はコンパイルエラーです。空チェックは size で行います。 +```java// ❌ +if (heap[1] != null) + +// ✅ +if (size > 0) + +③ pop と heap の代入がおかしい +java// ❌ heap に heap[size-1] の値を代入している +heap = heap[size - 1]; + +// ✅ sizeを減らすだけでいい +heap[1] = heap[size--]; +``` + +④ 動的拡張の書き方がおかしい +```java// ❌ heap に heap[size+1] は代入できない +heap = heap[size + 1]; + +// ✅ Arrays.copyOf で新しい配列を作る +heap = Arrays.copyOf(heap, heap.length * 2); +``` +⑤ heapify の比較が逆 +```java// ❌ 親が小さい時にswapしてしまう +if (heap[index] < heap[smallestIndex]) + +// ✅ 親が大きい時にswapする +if (heap[index] > heap[smallestIndex]) +``` + +⑥ hasLeftChild が null チェックになっている +```java// ❌ +return heap[leftChildIndex(index)] != null; + + +// ✅ +return leftChildIndex(index) <= size; +``` + +⑦ heapify で hasRightChild チェックが抜けている +```java// 右子が存在する場合だけ比較する +if (hasRightChild(index) && heap[rightChildIndex(index)] < heap[leftChildIndex(index)]) { +smallestIndex = rightChildIndex(index); +} + +修正点は多いですが、設計の流れ(add の Bubble Up、heapify の構造)は正しく理解できています!修正して貼ってみてください。 +``` + +### 2回目 +```java +class MinHeap { + // heap は int[] 配列とする + int[] heap; + // insert, pop, 時にheapの数を管理する + int size = 0; + // 初期容量を覚えておく + int maxSize; + + public MinHeap(int maxSize) { + this.heap = new int[maxSize + 1]; + this.heap[0] = Integer.MIN_VALUE; + this.maxSize = maxSize; +// size++; 番兵は要素ではないのでカウントアップしない + } + + public int pop() { +// if (heap[1] != null) { int にnullチェックはできない + if (size > 0) { + int result = heap[1]; + heap[1] = heap[size]; + size--; + heapify(1); + return result; + } else { + throw new ArrayIndexOutOfBoundsException("minheap don't have root"); + } + } + + public int peek() { + // heap[1]を返すだけ, 一応nullチェック + if (size > 0) { + return heap[1]; + } else { + throw new ArrayIndexOutOfBoundsException("minheap don't have root"); + } + } + + public void add(int element) { + // size == maxValue となった時、heapのサイズは heap[size++] として拡張する + if (isFull()) { + // size が maxSize+1 と同値になった時 heap[] が格納できるサイズ上限に達したのでサイズを増やす + heap = Arrays.copyOf(heap, heap.length * 2); + maxSize = heap.length - 1; + } + heap[++size] = element; + // minHeap なので、末尾に加える。親と比較して小さいならswapするを、再帰的に行う + // この書き方はちょっと冗長かな。。別関数に切り出した方が良いかも。。 + int index = size; + int parentIndex = parentIndex(index); + while (heap[index] < heap[parentIndex]) { + swap(index, parentIndex); + index = parentIndex; + parentIndex = parentIndex(index); + } + } + + private boolean isFull() { + return size == maxSize + 1; + } + + private void heapify(int index) { + if (!hasLeftChild(index)) { + return; + } + + int smallestIndex = leftChildIndex(index); + if (hasRightChild(index) && heap[rightChildIndex(index)] < heap[leftChildIndex(index)]) { + smallestIndex = rightChildIndex(index); + } + if (heap[index] > heap[smallestIndex]) { + swap(index, smallestIndex); + heapify(smallestIndex); + } + } + + private int parentIndex(int index) { + return index / 2; + } + + private int leftChildIndex(int index) { + return index * 2; + } + + private int rightChildIndex(int index) { + return index * 2 + 1; + } + + private boolean hasLeftChild(int index) { + return leftChildIndex(index) <= size; + } + + private boolean hasRightChild(int index) { + return rightChildIndex(index) <= size; + } + + private void swap(int i, int j) { + int temp = heap[i]; + heap[i] = heap[j]; + heap[j] = temp; + } +} +``` \ No newline at end of file From 768d638c15a321426cc1642b3538e205936d06ba Mon Sep 17 00:00:00 2001 From: busker <165013324+hiroki-horiguchi-dev@users.noreply.github.com> Date: Fri, 20 Mar 2026 18:11:07 +0900 Subject: [PATCH 2/3] Update 307.md --- heap-priorityQueue/307.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heap-priorityQueue/307.md b/heap-priorityQueue/307.md index 7bb475c..2bcec36 100644 --- a/heap-priorityQueue/307.md +++ b/heap-priorityQueue/307.md @@ -1,6 +1,6 @@ # 1st - 問題: [347. Top K Frequent Elements](https://leetcode.com/problems/top-k-frequent-elements/) -- コメント集: []() +- コメント集: [Top k frequent elements](https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/mobilebasic#h.dkkbub5o1tvz) - 過去に解いたことがあるため、1stのみです - 方針2つ - bucketソート From 2c5af2d7046349aaaf68ad1be595cde945e6fb8a Mon Sep 17 00:00:00 2001 From: busker <165013324+hiroki-horiguchi-dev@users.noreply.github.com> Date: Fri, 20 Mar 2026 18:12:24 +0900 Subject: [PATCH 3/3] Update 347.md --- heap-priorityQueue/{307.md => 347.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename heap-priorityQueue/{307.md => 347.md} (100%) diff --git a/heap-priorityQueue/307.md b/heap-priorityQueue/347.md similarity index 100% rename from heap-priorityQueue/307.md rename to heap-priorityQueue/347.md