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
65 changes: 65 additions & 0 deletions problems/373.find-k-pairs-with-smallest-sums/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
## step1
- 一旦愚直に解く方法を考える
- 選び方はnums1.length*nums2.length通りで、合計でsortする場合O(NlogN)
- 10^5*10^5 + 10^10log10 が大体 10^10(1+3.1)で1秒間に10^8回計算できる場合は410秒でTLE

- 改善案
- 取り方的にkを超えるindexは無視して良さそう
- その場合でも4.1秒でTLEしそう
- 全てのパターンを列挙するので1秒掛かる
- 一旦実装したが、Memory Limit Exceededになった

```py
def kSmallestPairs(
self, nums1: List[int], nums2: List[int], k: int
) -> List[List[int]]:
sum_combibations = []
for num1 in nums1[:k]:
for num2 in nums2[:k]:
sum_combibations.append([num1 + num2, [num1, num2]])

sum_combibations.sort()

return [combination for [_sum, combination] in sum_combibations[:k]]
```

- 愚直に列挙するやり方は無理そうなので前から数字を見ていき、取り方のルールを整理する[nums1のindex, nums2のindex]とする
- 1番目の答え: [0,0]
- 2番目の答えの候補:[0,1] or [1,0]
- 3番目の答えの候補
- 2番目の答えが[0,1]: [1,0] or [0,2]
- 2番目の答えが[1,0]: [0,1] or [2,1]
- 4番目の答えの候補
- 3番目の答えが[1,0]2番目の答えが[0,1]: [1,1] or [2,0] or [0,2]
- n=>n+1の時にindexが戻ることがあるのが嫌すぎる
- これまでの履歴から答えの候補になるindexは決まっているのでその中から都度選んでいくのがいい?
- 合計とindexをheapqに入れておいてpopしていく作戦
- 最初の提出では重複して同じペアをカウントしてしまったが、その後修正したらAcceptedになった

## step2
- 次のindexの存在確認とリストへの追加のところがごちゃごちゃしてるように感じるし、繰り返しているように見える
- 時間計算量はO(klogk),空間計算量はO(k)だと思う
- 他の人のコードを読む
- https://github.com/Hiroto-Iizuka/coding_practice/pull/10/files
- 十分に理解できておらず、swapさせるうまみみたいなのがわからなかった
- https://github.com/mamo3gr/arai60/pull/10/files
- `空間計算量を求めたあと、実際のどの程度のメモリを使用するか推定されましたでしょうか?もししていないのであれば、推定することをお勧めいたします。推定するには、時間計算量の式に最大サイズを代入し、 1 要素当たりのサイズを掛けてみてください。`. `sys.getsizeof`など勉強になった
- 自分で言うところのsum_heapは最小値候補なので、`candidates`とした方が良いように思った
- 自分の命名だと意味が付け加えられていないように感じた
- `enqueue_if_valid_and_not_visited`確かにわかりやすい
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://github.com/Yuto729/LeetCode_arai60/pull/16/files
- `C++: 約 1~10 億ステップ/秒`
- `CPU の周波数が数 GHz 程度であるため、IPC(Instructions per Cycle)を 1 と仮定すると、1 秒間に数十億命令を実行できます。C++ はオーバーヘッドが小さく、1 ステップを数命令〜数十命令で実行できるため、おおよそ 1~10 億ステップ/秒が目安となります。`
- `Python: 約 100 万~1000 万ステップ/秒`
- `インタープリタ方式によるオーバーヘッドが大きく、C++ の約 100 倍遅いため、この程度が目安になります。`

- 一旦、`enqueue_if_valid_and_not_visited`の対応を行う
- 個人的には`visited_pair_set`にすると候補というニュアンスが薄れる気がする
- candidateにaddしたという意味で`processed`を使っていたがなんでも`process`なので`added`とかにしておく

## step3
- 関数の中でnum1_index, num2_indexと書くと、読む量が増えてくどく感じたのでi,jにした
- n1_indexでも十分伝わると思ったので少し省略した
- データ構造を名前に含めがちな癖があるので`added_indice_set`とせず`added_indice`とした


51 changes: 51 additions & 0 deletions problems/373.find-k-pairs-with-smallest-sums/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#
# @lc app=leetcode id=373 lang=python3
#
# [373] Find K Pairs with Smallest Sums
#

# @lc code=start
import heapq


class Solution:
def kSmallestPairs(
self, nums1: List[int], nums2: List[int], k: int
) -> List[List[int]]:
sum_heap = [[nums1[0] + nums2[0], [0, 0]]]

k_small_pairs = []
processed_pair_set = set((0, 0))

while len(k_small_pairs) < k:
[_, num1_num2_index] = heapq.heappop(sum_heap)
[num1_index, num2_index] = num1_num2_index
k_small_pairs.append([nums1[num1_index], nums2[num2_index]])
if (
num1_index + 1 < len(nums1)
and (num1_index + 1, num2_index) not in processed_pair_set
):
heapq.heappush(
sum_heap,
[
nums1[num1_index + 1] + nums2[num2_index],
[num1_index + 1, num2_index],
],
)
processed_pair_set.add((num1_index + 1, num2_index))
if (
num2_index + 1 < len(nums2)
and (num1_index, num2_index + 1) not in processed_pair_set
):
heapq.heappush(
sum_heap,
[
nums1[num1_index] + nums2[num2_index + 1],
[num1_index, num2_index + 1],
],
)
processed_pair_set.add((num1_index, num2_index + 1))
return k_small_pairs


# @lc code=end
45 changes: 45 additions & 0 deletions problems/373.find-k-pairs-with-smallest-sums/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#
# @lc app=leetcode id=373 lang=python3
#
# [373] Find K Pairs with Smallest Sums
#

# @lc code=start
import heapq


class Solution:
def kSmallestPairs(
self, nums1: List[int], nums2: List[int], k: int
) -> List[List[int]]:
candidate = []

k_small_pairs = []
added_pair_set = set((0, 0))

def enqueue_if_valid_and_not_added(num1_index, num2_index):
if num1_index >= len(nums1) or num2_index >= len(nums2):
return
if (num1_index, num2_index) in added_pair_set:
return
heapq.heappush(
candidate,
[
nums1[num1_index] + nums2[num2_index],
[num1_index, num2_index],
],
)
added_pair_set.add((num1_index, num2_index))

enqueue_if_valid_and_not_added(0, 0)

while len(k_small_pairs) < k:
[_, num1_num2_index] = heapq.heappop(candidate)
[num1_index, num2_index] = num1_num2_index
k_small_pairs.append([nums1[num1_index], nums2[num2_index]])
enqueue_if_valid_and_not_added(num1_index + 1, num2_index)
enqueue_if_valid_and_not_added(num1_index, num2_index + 1)
return k_small_pairs


# @lc code=end
40 changes: 40 additions & 0 deletions problems/373.find-k-pairs-with-smallest-sums/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#
# @lc app=leetcode id=373 lang=python3
#
# [373] Find K Pairs with Smallest Sums
#

# @lc code=start

import heapq


class Solution:
def kSmallestPairs(
self, nums1: List[int], nums2: List[int], k: int
) -> List[List[int]]:
candidate = []
added_indice = set()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

add はsetの操作なので、実際の意味にできるとよりよいのではないでしょうか。すぐに思いついたのは used, あるいは検証するという意味で checked, tested などj

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.

ご指摘ありがとうございます。
毎回実際の意味にできておらずデータ構造への操作の名前にしてしまうよう癖づいてしまっていることを認識できました。
候補を挙げていただいた中だと特にcheckedがしっくりきました。


smallest_pairs = []

def enqueue_if_valid_and_not_added(i, j):
if i >= len(nums1) or j >= len(nums2):
return
if (i, j) in added_indice:
return
heapq.heappush(candidate, [nums1[i] + nums2[j], [i, j]])
added_indice.add((i, j))

enqueue_if_valid_and_not_added(0, 0)

while len(smallest_pairs) < k:
_sum, [n1_index, n2_index] = heapq.heappop(candidate)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

_ で始まる変数は、内部(未公開)であることを表す慣習があるように思います。予約語との衝突回避なら sum_, 単に無視するなら _, 和であることを示しながら無視するなら sum_ignored などの選択肢もありそうです。

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.

元々は無視をしていい変数だが和が入っていますというニュアンスで_sumとしておりました。
sumが入っていることは今回のケースだと意識しなくて良いので今度は_とするようにいたします。
またsumが入っていることをどうしても示したい時はsum_ignoredとするようにします。

smallest_pairs.append([nums1[n1_index], nums2[n2_index]])
enqueue_if_valid_and_not_added(n1_index + 1, n2_index)
enqueue_if_valid_and_not_added(n1_index, n2_index + 1)

return smallest_pairs


# @lc code=end