Skip to content
44 changes: 44 additions & 0 deletions 373. Find K Pairs with Smallest Sums/note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 373. Find K Pairs with Smallest Sums

## step1
時間計算量: O(klogk), 空間計算量: O(k)
全探索しか思い浮かばず、ACできなかった。
回答をみて理解してその通りに実装(変数名のリネームはおこなった。)
大まかな思考の流れは以下の通り
1, 単純に全探索→ソートすると計算量がよくないので、改善のためにどのように比較をするかを考える
2, 実際に比較をするのは、最小のペア(nums1[index1], nums2[index2])を基準に考えると、次の最小値は(nums1[index1 + 1], nums2[index2], nums1[index1], nums2[index2 + 1])かもしくは、この以前のステップでのこったもののどれかとなる。
3, 上記で以前のステップでのこったものを実現すること、常にソートされた状態でいることを考慮してHeapを使うという流れとなる。
4, あとは追加の際に重複をさけるため、Setを用意して重複のIndexペアは弾くようにする。

## step2
他の人の実装を参考に実装した。
### step2-1
参考:https://github.com/seal-azarashi/leetcode/pull/10/files
根本的な解法はstep1と同じだが、PriorityQueueにいれる中身を変えて、変数を少なくしたタイプ。変数宣言での記述量は増えるものの、繰り返しの部分は余計な合計などもなくわかりやすいかなとおもった。

### step2-2
参考:[https://github.com/seal-azarashi/leetcode/pull/10/files](https://github.com/kazukiii/leetcode/pull/11/files)
実装していて、indexまわりをまとめて独自で定義したほうが見やすいなあとおもっていて同様のコメントが上記のPRでもあったので、独自定義する方針でも実装してみた。

## メモ
Loopの表現についてfor, whileの議論が他のPRのコメントでもあったが、宣言的か手続的のどちらがいいかというものがあり、今回の問題だと個人的にはどちらでもそこまで変わらないかなと思ったが問題や条件によってはどちらが自然かというのを常に意識したい。
また問題文の制約次第ではループのなかでエラーとなる可能性があることも意識したい。
https://github.com/sakupan102/arai60-practice/pull/11#discussion_r1622031840
https://github.com/fhiyo/leetcode/pull/13/files

## step3
いただいたコメントを元に修正した。
### step3-1
アルゴリズムの修正を行なった。
・2つの配列について片方の配列のIndexしか動かさない
・動かす方のindex = 0の時のみもう片方の配列のについてその配列のIndex+1の要素をQueueに追加
することで重複がないようにおこなうことができる。

### step3-2
独自クラスの定義のし直し、および共通ロジックを関数として外出しした。
修正を行うと途端にどこが共通化されているのか、どこが無駄な処理になるのかがぼやけるので、全体の処理を頭に置きながら共通化部分をしっかり頭で描いてからコードにうつるように癖づけないといけないと思った。
また、HashSetに独自クラスのオブジェクトをいれるときに、仕組みとしてどのように一致判定を行なっているかについて気になったので以下にまとめる。

・HashSetではAbstractSetをimplementしており、equalsとhashCodeを使ってObjectの一致判定をしている。
・classは暗黙的にObjectクラスを継承しているのでequalsとhashCodeは必ずもっている。
・独自クラスでかつ一致判定をカスタムしたい時は上記をOverrideする必要がある。
28 changes: 28 additions & 0 deletions 373. Find K Pairs with Smallest Sums/step1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class Solution {
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
int length1 = nums1.length;
int length2 = nums2.length;
List<List<Integer>> kSmallestPairs = new ArrayList<>();
Set<Pair<Integer, Integer>> visited = new HashSet<>();

PriorityQueue<int[]> minSums = new PriorityQueue<>((a, b) -> a[0] - b[0]);
minSums.add(new int[] {nums1[0] + nums2[0], 0, 0});
visited.add(new Pair<Integer, Integer>(0, 0));

while(kSmallestPairs.size() < k) {
int[] currentMinPair = minSums.poll();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

currentMinPairといいつつ実際の要素数が3つなのは少し気になりました。
良い名前思いつかないんですがtotalAndIndexesとかですかね...

int index1 = currentMinPair[1];
int index2 = currentMinPair[2];
kSmallestPairs.add(List.of(nums1[index1], nums2[index2]));
if (index1 + 1 < length1 && !visited.contains(new Pair<Integer, Integer>(index1 + 1, index2))) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(index1 + 1, index2)の要素を入れるのは index2が0のときのみ、とすると重複が発生せずvisitedが不要になりそうです。

            if (index2 == 0 && index1 + 1 < length1) {

minSums.add(new int[] {nums1[index1 + 1] + nums2[index2], index1 + 1, index2});
visited.add(new Pair<Integer, Integer>(index1 + 1, index2));
}
if (index2 + 1 < length2 && !visited.contains(new Pair<Integer, Integer>(index1, index2 + 1))) {
minSums.add(new int[] {nums1[index1] + nums2[index2 + 1], index1, index2 + 1});
visited.add(new Pair<Integer, Integer>(index1, index2 + 1));
}
}
return kSmallestPairs;
}
}
28 changes: 28 additions & 0 deletions 373. Find K Pairs with Smallest Sums/step2-1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class Solution {
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
int length1 = nums1.length;
int length2 = nums2.length;
List<List<Integer>> kSmallestPairs = new ArrayList<>();
Set<Pair<Integer, Integer>> visited = new HashSet<>();

PriorityQueue<int[]> minSortedPairs = new PriorityQueue<>((a, b) -> (nums1[a[0]] + nums2[a[1]]) - (nums1[b[0]] + nums2[b[1]]));
minSortedPairs.add(new int[] {0, 0});
visited.add(new Pair<Integer, Integer>(0, 0));

while(kSmallestPairs.size() < k) {
int[] currentMinPair = minSortedPairs.poll();
int index1 = currentMinPair[0];
int index2 = currentMinPair[1];
kSmallestPairs.add(List.of(nums1[index1], nums2[index2]));
if (index1 + 1 < length1 && !visited.contains(new Pair<Integer, Integer>(index1 + 1, index2))) {
minSortedPairs.add(new int[] {index1 + 1, index2});
visited.add(new Pair<Integer, Integer>(index1 + 1, index2));
}
if (index2 + 1 < length2 && !visited.contains(new Pair<Integer, Integer>(index1, index2 + 1))) {
minSortedPairs.add(new int[] {index1, index2 + 1});
visited.add(new Pair<Integer, Integer>(index1, index2 + 1));
}
}
return kSmallestPairs;
}
}
40 changes: 40 additions & 0 deletions 373. Find K Pairs with Smallest Sums/step2-2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
class Solution {
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
int length1 = nums1.length;
int length2 = nums2.length;
List<List<Integer>> kSmallestPairs = new ArrayList<>();
Set<Pair<Integer, Integer>> visited = new HashSet<>();

PriorityQueue<SumPair> minSortedPairs = new PriorityQueue<>((a, b) -> (a.sum - b.sum));
minSortedPairs.add(new SumPair(nums1, 0, nums2, 0));
visited.add(new Pair<Integer, Integer>(0, 0));

for(int i = 0; i < k; i++) {
SumPair currentMinPair = minSortedPairs.poll();
int index1 = currentMinPair.indice.getKey();
int index2 = currentMinPair.indice.getValue();
kSmallestPairs.add(List.of(currentMinPair.elements.getKey(), currentMinPair.elements.getValue()));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

List.of(currentMinPair.elements.getKey(), currentMinPair.elements.getValue())

これ SumPair のメソッドにしてもいいんじゃないですか。

ほかも色々とメンバーにしてもいいかもしれませんね。

if (index1 + 1 < length1 && !visited.contains(new Pair<Integer, Integer>(index1 + 1, index2))) {
minSortedPairs.add(new SumPair(nums1, index1 + 1, nums2, index2));
visited.add(new Pair<Integer, Integer>(index1 + 1, index2));
}
if (index2 + 1 < length2 && !visited.contains(new Pair<Integer, Integer>(index1, index2 + 1))) {
minSortedPairs.add(new SumPair(nums1, index1, nums2, index2 + 1));
visited.add(new Pair<Integer, Integer>(index1, index2 + 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.

ここのところ、素直に日本語で表現すると、

  • PriorityQueue から取り出す
  • 答えに追加する
  • 右隣を作り、必要ならば PriorityQueue に追加する
  • 下隣を作り、必要ならば PriorityQueue に追加する
    くらいに書けると思うんですよ。ならば、そのようなメソッドを用意してもいいんじゃないでしょうか。

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.

ありがとうございます!共通処理の部分は別関数に切り出そうとおもいます

return kSmallestPairs;
}
}

class SumPair {
int sum;
Pair<Integer, Integer> elements;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

elements ではなく、nums1, nums2 への参照を持つのも一つでしょうか。

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.

indexだけでことたりそうかなともおもいましたので、シンプルに合計値とIndexだけをもつようにしました 

Pair<Integer, Integer> indice;

SumPair(int[] nums1, int index1, int[] nums2, int index2) {
this.sum = nums1[index1] + nums2[index2];
this.elements = new Pair(nums1[index1], nums2[index2]);
this.indice = new Pair(index1, index2);
}
}
24 changes: 24 additions & 0 deletions 373. Find K Pairs with Smallest Sums/step3-1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Solution {
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
int length1 = nums1.length;
int length2 = nums2.length;
List<List<Integer>> kSmallestPairs = new ArrayList<>();

PriorityQueue<int[]> minSums = new PriorityQueue<>((a, b) -> a[0] - b[0]);
minSums.add(new int[] { nums1[0] + nums2[0], 0, 0 });

while (kSmallestPairs.size() < k) {
int[] totalAndIndexes = minSums.poll();
int index1 = totalAndIndexes[1];
int index2 = totalAndIndexes[2];
kSmallestPairs.add(List.of(nums1[index1], nums2[index2]));
if (index2 == 0 && index1 + 1 < length1) {
minSums.add(new int[] { nums1[index1 + 1] + nums2[index2], index1 + 1, index2 });
}
if (index2 + 1 < length2) {
minSums.add(new int[] { nums1[index1] + nums2[index2 + 1], index1, index2 + 1 });
}
}
return kSmallestPairs;
}
}
64 changes: 64 additions & 0 deletions 373. Find K Pairs with Smallest Sums/step3-2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
class Solution {
int[] nums1;
int[] nums2;

public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
this.nums1 = nums1;
this.nums2 = nums2;
List<List<Integer>> kSmallestPairs = new ArrayList<>();
Set<IndexPair> visited = new HashSet<>();
PriorityQueue<IndexPair> minSortedPairs = new PriorityQueue<>((a, b) -> (a.sum - b.sum));
IndexPair firstPair = new IndexPair(0, 0, nums1[0] + nums2[0]);
minSortedPairs.add(firstPair);
visited.add(firstPair);

for(int i = 0; i < k; i++) {
IndexPair currentMinPair = minSortedPairs.poll();
int index1 = currentMinPair.index1;
int index2 = currentMinPair.index2;
kSmallestPairs.add(List.of(nums1[index1], nums2[index2]));
insertPairIfNeeded(minSortedPairs, visited, index1 + 1, index2);
insertPairIfNeeded(minSortedPairs, visited, index1, index2 + 1);
}
return kSmallestPairs;
}

private void insertPairIfNeeded(PriorityQueue<IndexPair> queue, Set<IndexPair> visited, int pos1, int pos2) {
int len1 = nums1.length;
int len2 = nums2.length;
if (len1 <= pos1 || len2 <= pos2) {
return;
}
IndexPair newPair = new IndexPair(pos1, pos2, nums1[pos1] + nums2[pos2]);
if (visited.contains(newPair)) {
return;
}
queue.add(newPair);
visited.add(newPair);
}
}

class IndexPair {
int sum;
int index1;
int index2;

IndexPair(int index1, int index2, int sum) {
this.index1 = index1;
this.index2 = index2;
this.sum = sum;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IndexPair other = (IndexPair) o;
return other.index1 == index1 && other.index2 == index2;
}

@Override
public int hashCode() {
return Objects.hash(index1, index2);
}
}