Skip to content
Open
Show file tree
Hide file tree
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
52 changes: 52 additions & 0 deletions arai60/find-k-pairs-with-smallest-sums/README.md
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しておく
35 changes: 35 additions & 0 deletions arai60/find-k-pairs-with-smallest-sums/step1.cpp
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;
}
};
};
38 changes: 38 additions & 0 deletions arai60/find-k-pairs-with-smallest-sums/step2.cpp
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;
}
};
};
36 changes: 36 additions & 0 deletions arai60/find-k-pairs-with-smallest-sums/step3.cpp
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;
Copy link

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>> にするのも一つと思います。

Copy link
Owner Author

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)

Choose a reason for hiding this comment

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

Copy link

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 の変え方を見ておきましょう。

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
Copy link

Choose a reason for hiding this comment

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

これ関数にしてもいいかもしれません。

うーん、なんとなく同じ記述が繰り返されている気がするので、整理できないでしょうか。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ここはうまく整理できそうです。step4で見直してみます。

Choose a reason for hiding this comment

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

具体的にはコンストラクタを定義すると良いですね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

@liquo-rice
ありがとうございます。コンストラクタを定義してみました。-> beec9a4
こちらでイメージ正しいでしょうか?

}
return answer;
}

private:
struct SumPair {
int sum;
vector<int> pair;
Copy link

Choose a reason for hiding this comment

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

pair は標準ライブラリーにある言葉なので避けたほうがよいでしょう。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ご指摘ありがとうございます。変数名をpairから見直します。

vector<int> indice;
Copy link

Choose a reason for hiding this comment

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

pair<int, int> で良くないでしょうか。

Copy link
Owner Author

@kazukiii kazukiii Jun 16, 2024

Choose a reason for hiding this comment

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

たしかにここは vector<int> より pair<int, int> の方が適切かと思いました。
step4で修正します。


bool operator<(const SumPair& other) const {
return sum > other.sum;
}
};
};
38 changes: 38 additions & 0 deletions arai60/find-k-pairs-with-smallest-sums/step4.cpp
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;
}
};
};