Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 264 additions & 0 deletions 03_Heap,PriorityQueue/703_Kth_Largest_Element_in_a_stream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
<問題>
https://leetcode.com/problems/kth-largest-element-in-a-stream/description/

# step1

5分程度答えを見ずに考えて、手が止まるまでやってみる。
何も思いつかなければ、答えを見て解く。
ただし、コードを書くときは答えを見ないこと。
正解したら一旦OK。
思考過程もメモしてみる。

```c++
class KthLargest {
public:

vector<int> scores;
int kth;
Comment on lines +16 to +17
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

外からアクセスが想定されていないものは private に。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

以前野田さんにおすすめされたのですが、「effective c++」にこの辺りのことが記載されておりますので読んでみるといいと思いました。


KthLargest(int k, vector<int>& nums) {
scores=nums;
sort(scores.rbegin(),scores.rend());
kth=k;
Comment on lines +20 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

スペースの空け方のルールはなにか統一的なものがあればなんでもいいと思います。
Google Style Guide は一つです。
https://google.github.io/styleguide/cppguide.html

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
リンク参考にさせていただきます。
違和感を持たれないようにコードを書くことを心がけます。

}

int add(int val) {
scores.push_back(val);
sort(scores.rbegin(),scores.rend());
if((int)size(scores)>kth){
return scores[kth-1];
}else{
return scores[kth-1];
}
Comment on lines +28 to +32
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ、条件分岐して同じ式ですね。それだったらまとめていいですが、なんか意図があったんじゃないでしょうか。全体の大きさを小さくするとか。kth より多いときには減らせますね。

(int) もこの場合は不要ですかね。

Copy link
Copy Markdown
Owner Author

@haniwachann haniwachann Nov 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません。
vectorの要素数がkthよりも大きいことが前提ならば必要ないですが、vectorの要素数がkthより小さいときはエラーを知らせるように書こうとしてました。

教えていただいた通り、vectorの長さを減らすようにします!

}
};

/**
* Your KthLargest object will be instantiated and called as such:
* KthLargest* obj = new KthLargest(k, nums);
* int param_1 = obj->add(val);
*/
```

【考えたこと】
- k番目を取り出すとのことなどで、要素くわえるごとにソートすればいいと思った。
- classのメンバ変数とメンバ関数、外部の関数との変数などのやりとりに慣れておらず、変数をpublicに用意しないといけないことなど少し戸惑う。もっとclassになれないといけない。
- k番目の値がないときは場合分けした。(想定しない値が入ったときの例外処理を書くことが自然にできないので、練習が必要。
- はじめのsortでO(NlogN)、追加ごとにO(n)をクエリk回分だけ実施する。

# step2
他の方が描いたコードを見て、参考にしてコードを書き直してみる。
参考にしたコードのリンクは貼っておく。
読みやすいことを意識する。
他の解法も考えみる。

計算量:O(N)
N:sの文字サイズ

```c++
class KthLargest {
public:

map<int,int> canditates;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multimap を使っても同様の処理が書けると思います。余裕があれば挑戦してみてください。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
multimapを使用する場合は、keyを問題で与えられた値として、keyの値がmultimap全体の中で何個目であるかをvalueに入れるイメージでしょうか。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

いえ、 multipmap の要素数が k を超えるたびに、一番小さい値を削除していくことになると思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multimapではkey,valueともに同じものが複数存在できるという理解でいいでしょうか。
https://cpprefjp.github.io/reference/map/multimap.html

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

しまった…。書き間違えました…。 multiset のほうでした…。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multiset教えていただきありがとうございます!
実装のイメージつきました!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらはcandidateの誤りでしょうか?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

スペルミスです🙏

int kth;

KthLargest(int k, vector<int>& nums) {
for(int i=0;i<k;i++){
if(canditates.count(nums[i])!=0){
canditates[nums[i]]++;
}else{
canditates[nums[i]]=1;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ない場合でも、アクセスするだけで作られて0になります。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

アクセスするだけで要素が作られること知りませんでした...
そのような知識はリファレンスを日頃から読んで少しづつ身につけていくのがよいでしょうか。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://cpprefjp.github.io/reference/map/map/op_at.html
「対応する要素が存在しない場合は、要素を値初期化して参照を返す。」
そうですね。とりあえず、読みましょう。
だから canditates[nums[i]]++; だけでいいですね。

}
}

for(int i=k;i<(int) size(nums);i++){
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なんだかfor, if, else文のスペースの開け方に違和感を感じてしまいます.
https://ttsuki.github.io/styleguide/cppguide.ja.html#Formatting_Looping_Branching
こちらにGoogle Coding Style Guideのifのスペースの開け方があります

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

違和感を教えていただきありがとうございます。
いただいたリンクに目を通して、少しずつ修正していきます!

if(canditates.count(nums[i])!=0){
canditates[nums[i]]++;
}else{
canditates[nums[i]]=1;
}

if(canditates.begin()->second==1){
canditates.erase(canditates.begin());
}else{
canditates.begin()->second--;
}
Comment on lines +81 to +85
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

とりあえず、1を引いて、0以下になっていたら消すという方法もあります。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
条件分岐のelseが1つ減ってわかりやすくなりそうです。

}

kth=k;
}

int add(int val) {
if(canditates.count(val)!=0){
canditates[val]++;
}else{
canditates[val]=1;
}

if(canditates.begin()->second==1){
canditates.erase(canditates.begin());
}else{
canditates.begin()->second--;
}

auto iter=canditates.begin();
int ans= iter->first;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ansだと何が格納されているのか分からないので、変数名から何が入っているのか分かるような変数名がいいと思いました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
kth_numとかがいいかと思いました。

return ans;
Comment on lines +104 to +106
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これくらいならば一行でもいいでしょう。

}
};

/**
* Your KthLargest object will be instantiated and called as such:
* KthLargest* obj = new KthLargest(k, nums);
* int param_1 = obj->add(val);
*/
```

まず(大きほうから)k番目以降を保持する必要がない。
すなわち、k個のデータ構造に1個追加されたときに、
一番小さいものを削除すればよい→min heapを使える。
https://github.com/doocs/leetcode/blob/main/solution/0700-0799/0703.Kth%20Largest%20Element%20in%20a%20Stream/README_EN.md

priority queueは一度実装しておいたほうがよい。
https://discord.com/channels/1084280443945353267/1192736784354918470/1194613857046503444

priority queueの前にmapを使った実装を考えるべき
step2ではmapをつかって実装したが、いまいちわかりにくいコードな気がする。
mapのキーを与えられた値として、値の個数を管理する。
mapはk個の値を持つ。

https://discord.com/channels/1084280443945353267/1183683738635346001/1185264362508795984
https://discord.com/channels/1084280443945353267/1200089668901937312/1202182322229882920

重複ありのmapもある
https://cpprefjp.github.io/reference/map/multimap.html

C++はmapが平衡2分木
https://pyteyon.hatenablog.com/entry/2019/01/01/220850#map-%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A02%E5%88%86%E6%9C%A8


選択肢が見えなさすぎる感じがする。
一つ見つけたら、思考停止してしまう。

- 参考

https://github.com/kazukiii/leetcode/pull/9/files
https://github.com/cheeseNA/leetcode/pull/12/files
https://github.com/TORUS0818/leetcode/pull/10/files
https://github.com/nittoco/leetcode/pull/11/files
https://github.com/Ryotaro25/leetcode_first60/pull/9/files
https://github.com/goto-untrapped/Arai60/pull/23/files

# step3

今度は、時間を測りながら、もう一回、書きましょう。書いてアクセプトされたら文字消してもう一回書く。これを10分以内に一回もエラーを出さずに書ける状態になるまで続ける。3回続けてそれができたらその問題はOK。

実施しました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

step4として、レビューを受けてコードを修正いたしました。

# step4_1

レビューを受けて、コードを修正する。
再度3回連続acceptされるまで続ける。

```c++
class KthLargest {
private:
map<int,int> canditates;
int kth;

public:
KthLargest(int k, vector<int>& nums) {
kth=k;

for (int i=0; i<k; i++) {
canditates[nums[i]]++;
}
for (int i=k; i<size(nums); i++) {
canditates[nums[i]]++;
canditates.begin()->second--;
if (canditates.begin()->second<=0) {
canditates.erase(canditates.begin());
}
Comment on lines +177 to +181
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L185-189とほとんど同じです。関数に切り出せますか?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
privateに関数を追加して処理をまとめました。

class KthLargest {
private:
    map<int,int> candidates;
    int kth;
    void add_num_to_candidates(int nums_add){
        candidates[nums_add]++;
        candidates.begin()->second--;
        if (candidates.begin()->second<=0) {
            candidates.erase(candidates.begin());
        }
    }
public:
    KthLargest(int k, vector<int>& nums) {
        kth=k;

        for (int i=0; i<k; i++) candidates[nums[i]]++;
        for (int i=k; i<size(nums); i++) add_num_to_candidates(nums[i]);
    }
    int add(int val) {
        add_num_to_candidates(val);
        return candidates.begin()->first;
    }
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

下のテストケースがパスしないようです。

["KthLargest","add","add","add","add","add"]
[[1,[]],[-3],[-2],[-4],[0],[4]]

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

初期配列が空だと
for (int i=0; i<k; i++) candidates[nums[i]]++;
のnums[i]で存在しない要素を呼び出していて、エラーが出るようでした。
for文の実行条件をi<k && i<size(nums)に修正しました。

また、add_num_to_candidatesの中で、配列の数がkthに満たない場合も
加えた値を削除してしまっていたので、candidates_counterの変数を用意して、
要素数がkthより大きい時のみ、削除する処理をするように条件分けしました。

class KthLargest {
private:
    map<int,int> candidates;
    int candidates_counter=0;
    int kth;
    void add_num_to_candidates(int nums_add){
        candidates[nums_add]++;
        candidates_counter++;
        if (candidates_counter>kth) {
            candidates.begin()->second--;
            if (candidates.begin()->second<=0) {
                candidates.erase(candidates.begin());
            }
        }
    }
public:
    KthLargest(int k, vector<int>& nums) {
        kth=k;

        for (int i=0; i<k && i<size(nums); i++){
            candidates[nums[i]]++;
            candidates_counter++;
        }
        for (int i=k; i<size(nums); i++) add_num_to_candidates(nums[i]);
    }
    int add(int val) {
        add_num_to_candidates(val);
        return candidates.begin()->first;
    }
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一回、自然言語にしてみるんですが、重複している感じがしませんか。
kth は、「この KthLargest のインスタンスがいくつまで管理するか」
candidates は、「大きいものから k 個(追加された数が k に満たない場合はすべて)の分布」
candidates_counter は、「消去したものも含めて追加された数」ですね。

add_num_to_candidates は、意味は「数字を追加して candidates の分布を更新」、操作としては「数字を追加し、k を超えていたら一番小さいのを消す」
KthLargest は、意味は「k と nums で初期化」、操作としては「入力の配列に対して、k 番目までは candidates を更新し、それ以降 add_num_to_candidates を呼ぶ」
add は、意味は「数字を追加して、k 番目に大きいものを返す」、操作としては「add_num_to_candidates を呼んで、k 番目を返す」

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

個人的には
KthLargest は add_num_to_candidates を nums に対して呼ぶのでよいでしょう。
add_num_to_candidates と add は一緒にしてしまうかもしれません。
candidates_counter は、意味を「中に入っている個数」にして消したら減らしたいです。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

変数、関数ともに自然言語にしてみると、
・addがadd_num_to_candidatesを呼んでるだけになっている。
・KthLargestのk番目とそれ以降で分けていることを、すでにadd_num_to_candidatesで処置している。
ことがよくわかります...

candidates_counterの変数の意味は中に入っている個数という意味で使いたかったのですが、
実装上は違う意味になっていました...
コードを頭の中でミスなく動かす練習をしていきたいです。

以下の通り直してみました。

class KthLargest {
private:
    map<int,int> candidates;
    int candidates_counter=0;
    int kth;

public:
    KthLargest(int k, vector<int>& nums) {
        kth=k;
        for (int i=0; i<size(nums); i++) add(nums[i]);
    }
    int add(int val) {
        candidates[val]++;
        candidates_counter++;
        if (candidates_counter>kth) {
            candidates.begin()->second--;
            if (candidates.begin()->second<=0) {
                candidates.erase(candidates.begin());
            }
            candidates_counter--;
        }
        return candidates.begin()->first;
    }
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

いいかと思います。
削除の際のcandidates.begin()は三回繰り返しているので変数においてもいいかもしれませんね。
他に気になるのは、スタイル(演算子の前後にスペースをいれるか、ぶら下がり for を使うか、インクリメント・デクリメントは前置が好きか、など)ですが、趣味の範囲です。

}
}
int add(int val) {
canditates[val]++;
canditates.begin()->second--;
if (canditates.begin()->second<=0) {
canditates.erase(canditates.begin());
}
return canditates.begin()->first;
}
};

/**
* Your KthLargest object will be instantiated and called as such:
* KthLargest* obj = new KthLargest(k, nums);
* int param_1 = obj->add(val);
*/
```

# step4_2
関数に処理をまとめました。

```C++
class KthLargest {
private:
map<int,int> candidates;
int kth;
void add_num_to_candidates(int nums_add){
candidates[nums_add]++;
candidates.begin()->second--;
if (candidates.begin()->second<=0) {
candidates.erase(candidates.begin());
}
}
public:
KthLargest(int k, vector<int>& nums) {
kth=k;

for (int i=0; i<k; i++) candidates[nums[i]]++;
for (int i=k; i<size(nums); i++) add_num_to_candidates(nums[i]);
}
int add(int val) {
add_num_to_candidates(val);
return candidates.begin()->first;
}
};
```

# step4_3
・初期配列が空の時を対処。
・配列がkth以下の時は削除処理をしないように修正。
```c++
class KthLargest {
private:
map<int,int> candidates;
int candidates_counter=0;
int kth;
void add_num_to_candidates(int nums_add){
candidates[nums_add]++;
candidates_counter++;
if (candidates_counter>kth) {
candidates.begin()->second--;
if (candidates.begin()->second<=0) {
candidates.erase(candidates.begin());
}
}
}
public:
KthLargest(int k, vector<int>& nums) {
kth=k;

for (int i=0; i<k && i<size(nums); i++){
candidates[nums[i]]++;
candidates_counter++;
}
for (int i=k; i<size(nums); i++) add_num_to_candidates(nums[i]);
}
int add(int val) {
add_num_to_candidates(val);
return candidates.begin()->first;
}
};
```