-
Notifications
You must be signed in to change notification settings - Fork 0
373. Find K Pairs with Smallest Sums #11
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,52 @@ | ||
| ## 考察 | ||
| - 初見の問題 | ||
| - 方針を考える | ||
| - ソートする | ||
| - time: O(n^2 log n), space: O(n^2) | ||
| - n <= 10^5 なので、計算ステップ数は最悪10^10オーダー -> ✕ | ||
| - メモリも例えばintを10^10持つことを考えると、4 * 10^10 bytes = 40GB -> ✕ | ||
| - サイズkの最大ヒープを使う | ||
| - time: O(n^2 log k), space: O(k) | ||
| - spaceは大丈夫そうだが、timeがきつそう -> ✕ | ||
| - 最小ヒープを使う | ||
| - 各配列がソートされているのをうまく使いたい | ||
| - sumが要素の行列を考えて、行列の各行にnums1の要素を対応させると、行がソート済みの行列が見える | ||
| - この構造は何度か見たことがある | ||
| - merge k sorted lists の要領で小さいものから順番に取り出していけそう | ||
| - Ref. https://leetcode.com/problems/merge-k-sorted-lists/description/ | ||
| - メモリ上、あらかじめ全てのsumを計算しておくことができないので、その都度計算するように工夫する必要がありそう | ||
| - time: O(n log n + k log n), space: O(n) | ||
| - たぶんいける | ||
| - 最小ヒープを使う方針でやってみる | ||
| - あとは実装 | ||
|
|
||
| ## Step1 | ||
| - 上記のアルゴリズムを実装 | ||
| - time: O(n log n + k log n), space: O(n) | ||
| - パスはしたけど、かなり遅い(Runtime beats 5.05%) | ||
|
|
||
| ## Step2 | ||
| - Solutionsを覗いてみた | ||
| - サイズkの最大ヒープを使う O(n^2 * log k) のアルゴリズムを適切に枝狩りすることでもっと速度を出せるみたい | ||
| - この方針でもやってみる | ||
| - 実装してみて、結構自然な考え方かもしれないが、計算量評価が難しく感じた | ||
|
|
||
| ## Step3 | ||
| - Step1のアルゴリズムの方がしっくり来たため、そちらで実装した | ||
| - 1回目: 9m16s | ||
| - 2回目: 8m57s | ||
| - 3回目: 8m52s | ||
|
|
||
| ## Step4 | ||
| - 行列が縦方向にもソートされていたことに気づいたので、ロジックを少し修正 | ||
| - nums1.size() > kのとき、上からk行だけ考えればよかった | ||
| - k + 1行目より先に必ず上のk個が選ばれるため | ||
| - レビューを元に修正 | ||
| - 変数名の変更 | ||
| - pair -> elements (pairはstdの予約語のため) | ||
| - SumPairのelementsとindiceは要素数が必ず2つなので型を変更 | ||
| - vector<int> -> pair<int, int> | ||
| - SumPairにコンストラクタを定義して、同じ記述を集約 | ||
| - emplace_backやemplaceを使うことでコンストラクタの引数を直接受け取り、コンテナの末尾に新しい要素を直接構築できる | ||
| - moveすることで構造体のコピーを防ぎ、所有権だけ移動できる | ||
| - answerは要素数がkであることが確定しているので、先にメモリをreserveしておく |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| class Solution { | ||
| public: | ||
| vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) { | ||
| priority_queue<SumPair> min_sum_pair; | ||
| for (int i = 0; i < nums1.size(); ++i) { | ||
| int sum = nums1[i] + nums2[0]; | ||
| min_sum_pair.push({sum, {nums1[i], nums2[0]}, {i, 0}}); | ||
| } | ||
|
|
||
| vector<vector<int>> answer; | ||
| while (answer.size() < k) { | ||
| SumPair sum_pair = min_sum_pair.top(); | ||
| min_sum_pair.pop(); | ||
| answer.push_back(sum_pair.pair); | ||
|
|
||
| int index_nums1 = sum_pair.indice[0]; | ||
| int index_nums2 = sum_pair.indice[1]; | ||
| if (++index_nums2 >= nums2.size()) continue; | ||
| int sum = nums1[index_nums1] + nums2[index_nums2]; | ||
| min_sum_pair.push({sum, {nums1[index_nums1], nums2[index_nums2]}, {index_nums1, index_nums2}}); | ||
| } | ||
| return answer; | ||
| } | ||
|
|
||
| private: | ||
| struct SumPair { | ||
| int sum; | ||
| vector<int> pair; | ||
| vector<int> indice; | ||
|
|
||
| bool operator<(const SumPair& other) const { | ||
| return sum > other.sum; | ||
| } | ||
| }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| class Solution { | ||
| public: | ||
| vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) { | ||
| priority_queue<SumPair> min_sum_pairs; | ||
| int n = nums1.size(); | ||
| int m = nums2.size(); | ||
| for (int i = 0; i < n; ++i) { | ||
| for (int j = 0; j < m; ++j) { | ||
| int sum = nums1[i] + nums2[j]; | ||
| if (min_sum_pairs.size() < k) { | ||
| min_sum_pairs.push({sum, {nums1[i], nums2[j]}}); | ||
| } else if (min_sum_pairs.size() == k && sum < min_sum_pairs.top().sum) { | ||
| min_sum_pairs.pop(); | ||
| min_sum_pairs.push({sum, {nums1[i], nums2[j]}}); | ||
| } else { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| vector<vector<int>> answer; | ||
| while (!min_sum_pairs.empty()) { | ||
| answer.push_back(min_sum_pairs.top().pair); | ||
| min_sum_pairs.pop(); | ||
| } | ||
| return answer; | ||
| } | ||
|
|
||
| private: | ||
| struct SumPair { | ||
| int sum; | ||
| vector<int> pair; | ||
|
|
||
| bool operator<(const SumPair& other) const { | ||
| return sum < other.sum; | ||
| } | ||
| }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| class Solution { | ||
| public: | ||
| vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) { | ||
| vector<SumPair> sum_pairs; | ||
| for (int i = 0; i < nums1.size(); ++i) { | ||
| int sum = nums1[i] + nums2[0]; | ||
| sum_pairs.push_back({sum, {nums1[i], nums2[0]}, {i, 0}}); | ||
| } | ||
|
|
||
| priority_queue<SumPair> min_sum_pairs(sum_pairs.begin(), sum_pairs.end()); | ||
| vector<vector<int>> answer; | ||
| while (answer.size() < k) { | ||
| SumPair sum_pair = min_sum_pairs.top(); | ||
| min_sum_pairs.pop(); | ||
| answer.push_back(sum_pair.pair); | ||
|
|
||
| int index_nums1 = sum_pair.indice[0]; | ||
| int index_nums2 = sum_pair.indice[1]; | ||
| if (++index_nums2 >= nums2.size()) continue; | ||
| int sum = nums1[index_nums1] + nums2[index_nums2]; | ||
| min_sum_pairs.push({sum, {nums1[index_nums1], nums2[index_nums2]}, {index_nums1, index_nums2}}); | ||
|
Comment on lines
+20
to
+21
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. これ関数にしてもいいかもしれません。 うーん、なんとなく同じ記述が繰り返されている気がするので、整理できないでしょうか。
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. ここはうまく整理できそうです。step4で見直してみます。 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. 具体的にはコンストラクタを定義すると良いですね。
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. @liquo-rice |
||
| } | ||
| return answer; | ||
| } | ||
|
|
||
| private: | ||
| struct SumPair { | ||
| int sum; | ||
| vector<int> pair; | ||
|
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. pair は標準ライブラリーにある言葉なので避けたほうがよいでしょう。
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. ご指摘ありがとうございます。変数名をpairから見直します。 |
||
| vector<int> indice; | ||
|
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. pair<int, int> で良くないでしょうか。
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. たしかにここは |
||
|
|
||
| bool operator<(const SumPair& other) const { | ||
| return sum > other.sum; | ||
| } | ||
| }; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| class Solution { | ||
| public: | ||
| vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) { | ||
| vector<SumPair> sum_pairs; | ||
| for (int i = 0; i < nums1.size() && i < k; ++i) { | ||
| sum_pairs.emplace_back(nums1, nums2, i, 0); | ||
| } | ||
|
|
||
| priority_queue<SumPair> min_sum_pairs(sum_pairs.begin(), sum_pairs.end()); | ||
| vector<vector<int>> answer; | ||
| answer.reserve(k); | ||
| while (answer.size() < k) { | ||
| SumPair sum_pair = move(min_sum_pairs.top()); | ||
| min_sum_pairs.pop(); | ||
| answer.push_back({sum_pair.elements.first, sum_pair.elements.second}); | ||
|
|
||
| int index_nums1 = sum_pair.indice.first; | ||
| int index_nums2 = sum_pair.indice.second; | ||
| if (++index_nums2 >= nums2.size()) continue; | ||
| min_sum_pairs.emplace(nums1, nums2, index_nums1, index_nums2); | ||
| } | ||
| return answer; | ||
| } | ||
|
|
||
| private: | ||
| struct SumPair { | ||
| int sum; | ||
| pair<int, int> elements; | ||
| pair<int, int> indice; | ||
|
|
||
| SumPair(vector<int>& nums1, vector<int>& nums2, int index_nums1, int index_nums2) | ||
| : sum(nums1[index_nums1] + nums2[index_nums2]), elements({nums1[index_nums1], nums2[index_nums2]}), indice({index_nums1, index_nums2}) {} | ||
|
|
||
| bool operator<(const SumPair& other) const { | ||
| return sum > other.sum; | ||
| } | ||
| }; | ||
| }; |
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.
C++11 以降は tuple<int, int, int> 以前は、pair<int, pair<int, int>> にするのも一つと思います。
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.
レビューありがとうございます。
tupleやpairを使う選択肢もあったのですが、以前この2つは原則使わずstructを定義した方が可読性の観点で良いのではないかというレビューをいただいたことがあり、ここでは意図的にstructを定義しています。自分も現時点ではstructを定義した方が読みやすさの観点で優るのではないかと考えてます。
こちらに関して小田さんの見解も是非伺いたいです。
関連コメントです↓
#10 (comment)
Ryotaro25/leetcode_first60#11 (comment)
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.
https://google.github.io/styleguide/cppguide.html#Structs_vs._Tuples
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.
あ、はい。そうですね。定義したほうが好ましいですね。ただ、単純なコードだと、< が自動的に定義されたりして読む量が減るので、作らなくてもいいのではないかと思うこともあります。
https://en.cppreference.com/w/cpp/container/priority_queue
Compare の変え方を見ておきましょう。