-
Notifications
You must be signed in to change notification settings - Fork 0
Create 347.md #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,353 @@ | ||
| # 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ソート | ||
| - 時間計算量: 定数落として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<Integer, Integer> frequency = new HashMap<>(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 型を Map<Integer, Integer> とするのをよく見かけます。このあたりは所属するチームの平均的な書き方に合わせることをお勧めいたします。 |
||
|
|
||
| for (int num : nums) { | ||
| frequency.put(num, frequency.getOrDefault(num, 0) + 1); | ||
| } | ||
|
|
||
| List<Integer>[] bucket = new List[nums.length + 1]; | ||
| for (int key : frequency.keySet()) { | ||
| int value = frequency.get(key); | ||
|
Comment on lines
+32
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. keyはnum, valueはfrequencyで良いかもです。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ありがとうございます。 |
||
| if (bucket[value] == null) { | ||
| bucket[value] = new ArrayList<Integer>(); | ||
| } | ||
| bucket[value].add(key); | ||
| } | ||
|
|
||
| int idx = 0; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. こちらのコメントをご参照ください。 |
||
| for (int i = bucket.length - 1; i >= 0 && idx < k; i--) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. idxがkになったら、breakされるように書かれているので、idx < kはなくても良いですかね。 |
||
| List<Integer> 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<Integer, Integer> frequency = new HashMap<>(); | ||
| for (int num : nums) { | ||
| frequency.put(num, frequency.getOrDefault(num, 0) + 1); | ||
| } | ||
|
|
||
| PriorityQueue<Map.Entry<Integer, Integer>> minHeap = new PriorityQueue<>((a, b) -> a.getValue() - b.getValue()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. データ構造ではなく、実際に入っている中身で変数名を決めると良いかもです。 私ならk_topsと名付けます。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ありがとうございます。 |
||
|
|
||
| for (Map.Entry<Integer, Integer> 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; | ||
| } | ||
| } | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
変数を使用箇所よりだいぶ前で定義をすると、読み手は使用箇所まで変数の定義を覚えていなければならず、短期記憶を圧迫してしまいます。変数は使用箇所の直前で定義することをお勧めいたします。