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
190 changes: 190 additions & 0 deletions 1011. Capacity To Ship Packages Within D Days.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
### Step1

- 二分探索の復習
- 欲しいもの: 入力のdays以内に運べる、最小のcapacity
- そのためにあるべき、ループを抜けた状態: capacity_small = capacity_largeの値が答え。それ以上のcapacityだと、かかる日にちがdays「以下」。それより小さいcapacityだと、かかる日にちがdays「より大きい」(daysを含まない)
- 各ループが終わった後の状態: capacity_small「より小さい」ならば、days「より」日数かかってしまう。capacity_large「以上」であるならば、days「以下」で必ずできる
- capacity_small ≤ X < capacity_largeのところが「非確定」
- 無限ループにならない: capacity_middle = (capacity_small + capacity_large) // 2なので、small ≤ middle < largeである。small = middle + 1、large = middleのどっちかなので、必ず区間はループごとに少なくとも1縮む
- if not weightsの処理、実務だとWarningのロギングとか出すのかな

```python
class Solution:
def caluculate_minimum_days_to_ship(self, weights, capacity):
days = 1
today_sum_weights = 0
for weight in weights:
if weight > capacity:
return sys.maxsize
if today_sum_weights + weight > capacity:
today_sum_weights = 0
days += 1
today_sum_weights += weight
return days

def shipWithinDays(self, weights: List[int], days: int) -> int:
if not weights:
return 0
capacity_small = 1
capacity_large = sum(weights) + 1
Copy link

Choose a reason for hiding this comment

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

capacity_large の意味は、この capacity ならば運べるということなので、days >= 1 を確認すれば sum(weights) が代入できるのではないでしょうか。

while capacity_small < capacity_large:
capacity_middle = (capacity_small + capacity_large) // 2
days_for_middle = self.caluculate_minimum_days_to_ship(weights, capacity_middle)
if days < days_for_middle:
capacity_small = capacity_middle + 1
else:
capacity_large = capacity_middle
return capacity_small
```

## Step2

- if not weightsの処理は呼び出される側の関数(caluculate_minimum_days_to_ship)につけた方がいいのかなあ。この関数も、wegithsが空のまま他から呼び出されるかもしれないし。

```python

class Solution:
def caluculate_minimum_days_to_ship(self, weights: List[int], capacity: int) -> int:
if not weights:
return 0
days = 1
today_sum_weights = 0
for weight in weights:
if weight > capacity:
return sys.maxsize
if today_sum_weights + weight > capacity:
today_sum_weights = 0
days += 1
today_sum_weights += weight
return days

def shipWithinDays(self, weights: List[int], days: int) -> int:
capacity_small = 1
capacity_large = sum(weights) + 1
while capacity_small < capacity_large:
capacity_middle = (capacity_small + capacity_large) // 2
days_for_middle = self.caluculate_minimum_days_to_ship(weights, capacity_middle)
if days < days_for_middle:
capacity_small = capacity_middle + 1
else:
capacity_large = capacity_middle
return capacity_small
```

- https://github.com/shining-ai/leetcode/pull/44/files
- 大まかな違い
- 二分探索をする側で、capacity_smallをmax(weights)まで狭めている
- 関数で、必要な日数を返すだけではなく、その日数で運べるかTrue/Falseまでを判定している
- 自分も書いてて微妙に迷ったが、自分の関数の構成の方が他の用途にも使える形になるかなあと思い、上のように書いた。(が、いろんな人のを読んでると結構みんなmax(weights)で書いてるから迷ってきた、、、)
- https://github.com/fhiyo/leetcode/pull/45/files
- いやー、daysが0以下の考慮が抜けてましたね。sum(weights) + 1が返ってくるのはやばい。
- Stepの後半で、関数を、必要な日数を返すやつからTrue/Falsse判定を返すやつに直している
- weight > capacityの判定がない方がコードが簡潔だという意見もある。ただ、capacity_smallがmax(weights)なのも、やや読む側に認知負荷があるように思うので、難しい
- https://github.com/sakupan102/arai60-practice/pull/45/files
- 変数名はlowやhighの方がよい?難しい
- https://github.com/SuperHotDogCat/coding-interview/pull/27/files
- bisectで関数化すると、変数名で頑張らなくてもいいかも。確かに。
- https://github.com/TORUS0818/leetcode/pull/46/files
- 日数の情報は脇役で使いたいというコメントも気持ちはわかる。bisectで関数化すればこれも解決しそう。
- caluculate_minimum_days_to_shipは他の用途でも汎用的に使えそうなので、個人的には作りたい気持ちがある。
- これを外部関数として定義すれば、テストやデバッグもやりやすいと思う
- やはり、weight > capacityの判定はあったほうがいい気がする
- https://github.com/olsen-blue/Arai60/pull/44/files
- 二分探索の左を、考えた上で0から1に変更している
- 確かに左端は閉区間なので、この方がいいのかなあ。
- オーバーした際の処理は、today_sum_weights += weightの処理をオーバーしてない時と共通化する方か個人的に好み
- https://github.com/saagchicken/coding_practice/pull/10/files
- remaingの初期値を0にして、daysの初期値も0にするのか〜。
- こっちのコードでいうと、today_sum_weightの初期値をcapacityにすることでdaysを0から始める感じ
- これどうかな〜迷うなあ
- https://github.com/Ryotaro25/leetcode_first60/pull/51/files
- ヘッダファイルのincludeについて勉強
- Makefileの書き方はよくわかっていない。いつかやった方がいい気もするが。
- 二分探索の初期値について
- 「capacity_small「より小さい」ならば、days「より」日数かかってしまう。capacity_large「以上」であるならば、days「以下」で必ずできる」が不変条件なので、capacity_largeの初期値はsum(weights)ではなくsum(weights) + 1がいい気もするが、なんとも迷う(sum(weights)でも間違いではない)
- https://github.com/hroc135/leetcode/pull/42/files
- https://cs.opensource.google/go/go/+/master:src/slices/sort.go;l=158
- Goの二分探索
- コメントに不変条件が書いてあり良いなと思った。
- rangeの実装
- https://github.com/python/cpython/blob/main/Objects/rangeobject.c#L318
- えーなんか想像と違った。startとstopとstepとlengthだけ保持してて、アクセスする時は等差数列の公式でやってる感じか
- だからO(1)でアクセスできるのか
- ドキュメントにも書いてあった。ちゃんと読んでなかった。

https://docs.python.org/ja/3/library/stdtypes.html#typesseq-range

> [`range`](https://docs.python.org/ja/3/library/stdtypes.html#range) 型が通常の [`list`](https://docs.python.org/ja/3/library/stdtypes.html#list) や [`tuple`](https://docs.python.org/ja/3/library/stdtypes.html#tuple) にまさる点は、[`range`](https://docs.python.org/ja/3/library/stdtypes.html#range) オブジェクトがサイズや表す範囲にかかわらず常に一定の (小さな) 量のメモリを使うことです (`start`、`stop`、`step` の値のみを保存し、後は必要に応じて個々の項目や部分 range を計算するためです)。
>
- 以下の実装は自分的には結構好きだが、冗長かもしれない

```python
class Solution:
def load_items_until_fill(
self, capacity: int, weights: List[int], num_loaded_so_far: int
) -> int:
remaining_capacity = capacity
num_loaded = num_loaded_so_far
while num_loaded < len(weights) and weights[num_loaded] <= remaining_capacity:
remaining_capacity -= weights[num_loaded]
num_loaded += 1
return num_loaded

def calucalate_needed_day_to_ship(self, weights: List[int], capacity: int) -> int:
needed_day = 0
num_loaded_so_far = 0
while num_loaded_so_far < len(weights):
num_loaded = self.load_items_until_fill(
capacity, weights, num_loaded_so_far
)
if num_loaded == num_loaded_so_far:
return sys.maxsize
Comment on lines +140 to +141
Copy link

Choose a reason for hiding this comment

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

これは少しテクニカルすぎるという気がしています。

たとえば、num_loaded, had_space_to_load = load_items_until_fill( などと、載せる余裕があったかを別に返す方が好ましいと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

なるほど、ちょうどその選択肢と迷ってましたが、自分が書いたのはあまり一般的ではないんですね。
ありがとうございます。

num_loaded_so_far = num_loaded
needed_day += 1
return needed_day

def shipWithinDays(self, weights: List[int], days: int) -> int:
assert days > 0

def bisect_key_helper(capacity):
return self.calucalate_needed_day_to_ship(weights, capacity) <= days

return bisect_left(range(sum(weights)), 1, key=bisect_key_helper)

```

## Step3

```python
class Solution:
def load_items_until_fill(
self, num_loaded_so_far: int, capacity: int, weights: List[int]
) -> int:
num_loaded = num_loaded_so_far
remaining_capacity = capacity
while num_loaded < len(weights) and weights[num_loaded] <= remaining_capacity:
remaining_capacity -= weights[num_loaded]
num_loaded += 1
return num_loaded

def caluculate_needed_days_to_ship(self, capacity: int, weights: List[int]) -> int:

Choose a reason for hiding this comment

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

日数の情報はキャパの判断材料にしかすぎないので脇役で使いたい気持ちがあります。
TORUS0818/leetcode#46 (comment)

Copy link

@olsen-blue olsen-blue Apr 20, 2025

Choose a reason for hiding this comment

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

キャパシティの最適解が存在しうる数直線を考える、二分探索で抽出する数直線上のある点(とりあえずmiddle)のキャパシティ値で運べるかが知りたくなる、運べるキャパかどうかbool値でチェックする関数が必要だと感じる、みたいな流れが個人的にはしっくりきます。

Copy link
Owner Author

Choose a reason for hiding this comment

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

うーむ、難しいですね。
個人的には、キャパを固定したあと、その日数を数えたいと思うきもちが強いのと、
割と汎用的に他のところでも使えそうな関数なので、作っておきたいきもちがあります

needed_days = 0
num_loaded_so_far = 0
while num_loaded_so_far < len(weights):
num_loaded = self.load_items_until_fill(num_loaded_so_far, capacity, weights)
if num_loaded == num_loaded_so_far:
return sys.maxsize
num_loaded_so_far = num_loaded
needed_days += 1
return needed_days

def shipWithinDays(self, weights: List[int], days: int) -> int:
if days <= 0:
raise ValueError("days must be greater than 0")

def binary_search_helper(capacity):
return self.caluculate_needed_days_to_ship(capacity, weights) <= days

return bisect_left(range(sum(weights)), 1, key=binary_search_helper)

```