From aa879b69f04a442aeefb283e6f0767872be8d237 Mon Sep 17 00:00:00 2001 From: Yusuke Katsuki Date: Mon, 31 Mar 2025 19:32:54 -0400 Subject: [PATCH 1/8] step1 --- .../solution.md | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 0703_Kth_Largest_Element_in_a_Stream/solution.md diff --git a/0703_Kth_Largest_Element_in_a_Stream/solution.md b/0703_Kth_Largest_Element_in_a_Stream/solution.md new file mode 100644 index 0000000..bb80bb3 --- /dev/null +++ b/0703_Kth_Largest_Element_in_a_Stream/solution.md @@ -0,0 +1,63 @@ +## Problem +// The URL of the problem + +## Step 1 +5分程度答えを見ずに考えて、手が止まるまでやってみる。 +何も思いつかなければ、答えを見て解く。ただし、コードを書くときは答えを見ないこと。 +動かないコードも記録する。 +正解したら一旦OK。思考過程もメモする。 + +### Approach +* わからなかったので回答を見て作成 +* k 番目に大きい値だけを求めるために、サイズ k の最小ヒープを作り「常に上位 k 個の中で最小の値(= k 番目に大きい値)を根に持つ」よう管理するという方法。 + +```java +class KthLargest { + private final PriorityQueue minHeap; + private final int k; + + public KthLargest(int k, int[] nums) { + this.k = k; + minHeap = new PriorityQueue<>(k); + + for (int num : nums) { + add(num); + } + } + + public int add(int val) { + if (minHeap.size() < k) { + minHeap.offer(val); // add + } else if (val > minHeap.peek()) { + minHeap.poll(); // remove root + minHeap.offer(val); + } + + return minHeap.peek(); + } +} + +/** + * Your KthLargest object will be instantiated and called as such: + * KthLargest obj = new KthLargest(k, nums); + * int param_1 = obj.add(val); + */ +``` + +## Step 2 +他の方が描いたコードを見て、参考にしてコードを書き直してみる。 +参考にしたコードのリンクは貼っておく。 +読みやすいことを意識する。 +他の解法も考えみる。 + +```java + +``` + +## Step 3 +今度は、時間を測りながら、もう一回書く。 +アクセプトされたら消すを3回連続できたら問題はOK。 + +```java + +``` From 82e8b7029d5fc3dab8c0969e4bd4b5bb18dbf0dc Mon Sep 17 00:00:00 2001 From: Yusuke Katsuki Date: Sun, 27 Apr 2025 20:52:00 -0400 Subject: [PATCH 2/8] Step1 --- .../solution.md | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/0703_Kth_Largest_Element_in_a_Stream/solution.md b/0703_Kth_Largest_Element_in_a_Stream/solution.md index bb80bb3..93dd804 100644 --- a/0703_Kth_Largest_Element_in_a_Stream/solution.md +++ b/0703_Kth_Largest_Element_in_a_Stream/solution.md @@ -1,15 +1,18 @@ ## Problem -// The URL of the problem + +https://leetcode.com/problems/kth-largest-element-in-a-stream/ ## Step 1 -5分程度答えを見ずに考えて、手が止まるまでやってみる。 + +5 分程度答えを見ずに考えて、手が止まるまでやってみる。 何も思いつかなければ、答えを見て解く。ただし、コードを書くときは答えを見ないこと。 動かないコードも記録する。 -正解したら一旦OK。思考過程もメモする。 +正解したら一旦 OK。思考過程もメモする。 ### Approach -* わからなかったので回答を見て作成 -* k 番目に大きい値だけを求めるために、サイズ k の最小ヒープを作り「常に上位 k 個の中で最小の値(= k 番目に大きい値)を根に持つ」よう管理するという方法。 + +- わからなかったので回答を見て作成。ヒープはこれまで慣れてなかったのでこの問題でしっかり目にインプットした +- k 番目に大きい値だけを求めるために、サイズ k の最小ヒープを作り「常に上位 k 個の中で最小の値(= k 番目に大きい値)を根に持つ」よう管理するという方法。 ```java class KthLargest { @@ -24,7 +27,7 @@ class KthLargest { add(num); } } - + public int add(int val) { if (minHeap.size() < k) { minHeap.offer(val); // add @@ -36,15 +39,10 @@ class KthLargest { return minHeap.peek(); } } - -/** - * Your KthLargest object will be instantiated and called as such: - * KthLargest obj = new KthLargest(k, nums); - * int param_1 = obj.add(val); - */ ``` ## Step 2 + 他の方が描いたコードを見て、参考にしてコードを書き直してみる。 参考にしたコードのリンクは貼っておく。 読みやすいことを意識する。 @@ -55,8 +53,9 @@ class KthLargest { ``` ## Step 3 + 今度は、時間を測りながら、もう一回書く。 -アクセプトされたら消すを3回連続できたら問題はOK。 +アクセプトされたら消すを 3 回連続できたら問題は OK。 ```java From bb152d3f7ff74c344e1e2de9fe4b49f47170f06a Mon Sep 17 00:00:00 2001 From: Yusuke Katsuki Date: Mon, 28 Apr 2025 19:05:41 -0400 Subject: [PATCH 3/8] Step2 --- .../solution.md | 94 ++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/0703_Kth_Largest_Element_in_a_Stream/solution.md b/0703_Kth_Largest_Element_in_a_Stream/solution.md index 93dd804..3d0192c 100644 --- a/0703_Kth_Largest_Element_in_a_Stream/solution.md +++ b/0703_Kth_Largest_Element_in_a_Stream/solution.md @@ -9,11 +9,16 @@ https://leetcode.com/problems/kth-largest-element-in-a-stream/ 動かないコードも記録する。 正解したら一旦 OK。思考過程もメモする。 -### Approach +### Approach 1. Heap を使った方法 -- わからなかったので回答を見て作成。ヒープはこれまで慣れてなかったのでこの問題でしっかり目にインプットした +- わからなかったので回答を見て作成。ヒープはこれまで慣れてなかったのでこの問題でしっかりめにインプットした。時間があれば自分で実装したいところだが一旦後回し +- PriorityQueue + - https://docs.oracle.com/javase/8/docs/api/java/util/PriorityQueue.html - k 番目に大きい値だけを求めるために、サイズ k の最小ヒープを作り「常に上位 k 個の中で最小の値(= k 番目に大きい値)を根に持つ」よう管理するという方法。 +時間計算量: 各 add が O(log k) → 合計 O(n log k) +空間計算量: O(k) + ```java class KthLargest { private final PriorityQueue minHeap; @@ -48,8 +53,93 @@ class KthLargest { 読みやすいことを意識する。 他の解法も考えみる。 +### Approach 2. TreeMap を使った方法 + +- https://github.com/Ryotaro25/leetcode_first60/pull/9/files#r1619710596 + - > この問題で priority_queue にいきなりいくのは、私は実は違和感があります。 + - > 平衡二分木が C++ だったら map があり、これは順番に並んでいます。 +- 平衡二分探索木 + - https://ja.wikipedia.org/wiki/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8 +- Java だと TreeMap がそれに当たるっぽい(赤黒木) + +- TreeMap にスコアを Key、当該スコア個数を Value として保存 + +```java +class KthLargest { + private final int k; + private TreeMap scores; + + public KthLargest(int k, int[] nums) { + this.k = k; + this.scores = new TreeMap<>(); + + for (int num : nums) { + scores.put(num, scores.getOrDefault(num, 0) + 1); + } + } + + public int add(int val) { + + scores.put(val, scores.getOrDefault(val, 0) + 1); + int rank = 0; + for (int key : scores.descendingKeySet()) { + rank += scores.get(key); + if (rank >= k) { + return key; + } + } + return -1; // dummy + } +} +``` + +- 上記の方法で試したところ、大量の add が走るテストケースで Time Limit Exceeded エラーとなった。要素削除がないため add のたびに scors が増え続けること原因 +- 以下は 常に Top k に相当する要素だけを残すようにサイズを k に保つ +- k 番目に大きい要素は TreeMap の最小 Key に該当する + +時間計算量: コンストラクタが O(n log k)、add 単発が O(log k)なので O(n log k) +空間計算量: O(k) + ```java +class KthLargest { + private final int k; + private TreeMap scores; + private int scoreCount; + + public KthLargest(int k, int[] nums) { + this.k = k; + this.scores = new TreeMap<>(); + this.scoreCount = 0; + for (int num : nums) { + add(num); + } + } + + public int add(int val) { + if (scoreCount < k) { + scores.put(val, scores.getOrDefault(val, 0) + 1); + scoreCount++; + } else { + int kthScore = scores.firstKey(); + if (val > kthScore) { + scores.put(val, scores.getOrDefault(val, 0) + 1); + scoreCount++; + updateScoreCount(kthScore); + scoreCount--; + } + } + return scores.firstKey(); + } + private void updateScoreCount(int key) { + int count = scores.get(key); + if (count == 1) { + scores.remove(key); + } else { + scores.put(key, count - 1); + } + } +} ``` ## Step 3 From 76d83a69bbdbdb90376cc4a77b34c82d0bd8ef6d Mon Sep 17 00:00:00 2001 From: Yusuke Katsuki Date: Mon, 28 Apr 2025 19:45:41 -0400 Subject: [PATCH 4/8] Step3 --- .../solution.md | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/0703_Kth_Largest_Element_in_a_Stream/solution.md b/0703_Kth_Largest_Element_in_a_Stream/solution.md index 3d0192c..d52a74f 100644 --- a/0703_Kth_Largest_Element_in_a_Stream/solution.md +++ b/0703_Kth_Largest_Element_in_a_Stream/solution.md @@ -36,7 +36,7 @@ class KthLargest { public int add(int val) { if (minHeap.size() < k) { minHeap.offer(val); // add - } else if (val > minHeap.peek()) { + } else if (minHeap.peek() < val) { minHeap.poll(); // remove root minHeap.offer(val); } @@ -148,5 +148,26 @@ class KthLargest { アクセプトされたら消すを 3 回連続できたら問題は OK。 ```java +class KthLargest { + private PriorityQueue scores; + private final int k; + + public KthLargest(int k, int[] nums) { + this.k = k; + this.scores = new PriorityQueue<>(); + for (int num : nums) { + this.add(num); + } + } + public int add(int val) { + if (scores.size() < k) { + scores.offer(val); + } else if (scores.peek() < val) { + scores.poll(); + scores.offer(val); + } + return scores.peek(); + } +} ``` From 0446b49fea6aef407b8e54f2ac449384701f5a5f Mon Sep 17 00:00:00 2001 From: Yusuke Katsuki Date: Fri, 2 May 2025 16:09:57 -0400 Subject: [PATCH 5/8] sorted array --- .../solution.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/0703_Kth_Largest_Element_in_a_Stream/solution.md b/0703_Kth_Largest_Element_in_a_Stream/solution.md index d52a74f..2d09b3a 100644 --- a/0703_Kth_Largest_Element_in_a_Stream/solution.md +++ b/0703_Kth_Largest_Element_in_a_Stream/solution.md @@ -171,3 +171,46 @@ class KthLargest { } } ``` + +## Step 4 + +コメントいただいて実装した + +### Approach 3.ソート配列 + +- https://github.com/katsukii/leetcode/pull/23/files#r2065369258 + - > 最初はソートで k 番目のスコアを求める、それを保持しつつ新しいスコアと比べて更新するとかでもこの問題は問題ないのでしょうか +- たしかこれでも解法としてありえそう + +- コンストラクタ: 空の配列をメンバ変数として用意し、for 文で nums の要素数分 add を呼び出す +- add: 引数の val を配列の適切な位置に挿入したあと要素数が k 個になるように調整し index 0 を返す +   - `Collections.binarySearch()` でソート済配列のどの位置に挿入されるか特定可能 + +```java +class KthLargest { + private final int k; + private List sortedList; + public KthLargest(int k, int[] nums) { + this.k = k; + this.sortedList = new ArrayList<>(); + + for (int num : nums) { + add(num); + } + } + + public int add(int val) { + int insertPosition = Collections.binarySearch(sortedList, val); + if (insertPosition < 0) { // if val isn't in sortedList + insertPosition = -(insertPosition + 1); + } + + sortedList.add(insertPosition, val); + + if (sortedList.size() > k) { + sortedList.remove(0); + } + return sortedList.get(0); + } +} +``` From 05a11a7e35fa53902698e1aa8cb26d25bc369429 Mon Sep 17 00:00:00 2001 From: Yusuke Katsuki Date: Fri, 2 May 2025 16:14:18 -0400 Subject: [PATCH 6/8] complexity --- 0703_Kth_Largest_Element_in_a_Stream/solution.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/0703_Kth_Largest_Element_in_a_Stream/solution.md b/0703_Kth_Largest_Element_in_a_Stream/solution.md index 2d09b3a..a880554 100644 --- a/0703_Kth_Largest_Element_in_a_Stream/solution.md +++ b/0703_Kth_Largest_Element_in_a_Stream/solution.md @@ -178,6 +178,9 @@ class KthLargest { ### Approach 3.ソート配列 +時間計算量: O(n log n) +空間計算量: O(k) + - https://github.com/katsukii/leetcode/pull/23/files#r2065369258 - > 最初はソートで k 番目のスコアを求める、それを保持しつつ新しいスコアと比べて更新するとかでもこの問題は問題ないのでしょうか - たしかこれでも解法としてありえそう From ca4d756f4253bfaa55ef8bf1070b9f217ce88cda Mon Sep 17 00:00:00 2001 From: Yusuke Katsuki Date: Fri, 2 May 2025 16:26:03 -0400 Subject: [PATCH 7/8] reflect comments --- .../solution.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/0703_Kth_Largest_Element_in_a_Stream/solution.md b/0703_Kth_Largest_Element_in_a_Stream/solution.md index a880554..7018093 100644 --- a/0703_Kth_Largest_Element_in_a_Stream/solution.md +++ b/0703_Kth_Largest_Element_in_a_Stream/solution.md @@ -46,6 +46,22 @@ class KthLargest { } ``` +上記に対していただいたコメント + +- https://github.com/katsukii/leetcode/pull/23/files#r2068473153 + - > この if-else if 文を読んでいて、else のケースが大丈夫なのかなというのを考えるのに少し時間が取られたのでもう少し素直に書ける余地があるかなと思います。 + - > とりあえず queu に突っ込んでしまって、要素がサイズを超えていれば、減らしてあげるみたいな感じのほうがシンプルかなと個人的には思います。 + - ```java + public int add(int val) { + scores.offer(val); + if (scores.size() > k) { + scores.poll(); + } + return scores.peek(); + } + ``` + - たしかにこちらの方がわかりやすい + ## Step 2 他の方が描いたコードを見て、参考にしてコードを書き直してみる。 @@ -142,6 +158,23 @@ class KthLargest { } ``` +上記に対しいただいたコメント + +- https://github.com/katsukii/leetcode/pull/23/files#r2065049233 + + - > 自分なら numScores と名付けると思います。チームの平均的な書き方に合わせることをお勧めいたします。 + - たしかに個数を表すなら num◯◯ の方が共通認識としてわかりやすいのはあるかもしれない + +- https://github.com/katsukii/leetcode/pull/23/files#r2067722954 + - > `scores.put(val, scores.getOrDefault(val, 0) + 1);` + - > 私はこの put と getOrDefault を一行に書くのは好みではないです。 + - > val を 2 回書かないならば compute を使うようなのもありますが、素直に 2 行にするのも一つです。 + - たしかに 2 行にした方が見やすい。今後気をつける + - ```java + int count = scores.getOrDefault(val, 0) + 1; + scores.put(val, count); + ``` + ## Step 3 今度は、時間を測りながら、もう一回書く。 From e13b7a8b9bddd07c87a9a82c43696122692b7454 Mon Sep 17 00:00:00 2001 From: Yusuke Katsuki Date: Fri, 2 May 2025 16:27:57 -0400 Subject: [PATCH 8/8] fix --- 0703_Kth_Largest_Element_in_a_Stream/solution.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/0703_Kth_Largest_Element_in_a_Stream/solution.md b/0703_Kth_Largest_Element_in_a_Stream/solution.md index 7018093..dca6120 100644 --- a/0703_Kth_Largest_Element_in_a_Stream/solution.md +++ b/0703_Kth_Largest_Element_in_a_Stream/solution.md @@ -139,9 +139,7 @@ class KthLargest { int kthScore = scores.firstKey(); if (val > kthScore) { scores.put(val, scores.getOrDefault(val, 0) + 1); - scoreCount++; updateScoreCount(kthScore); - scoreCount--; } } return scores.firstKey();