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
159 changes: 159 additions & 0 deletions problem44/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
## 取り組み方
- step1: 5分以内に空で書いてAcceptedされるまで解く + テストケースと関連する知識を連想してみる
- step2: 他の方の記録を読んで連想すべき知識や実装を把握した上で、前提を置いた状態で最適な手法を選択し実装する
- step3: 10分以内に1回もエラーを出さずに3回連続で解く

## step1
1日に取り込める最小のキャパを求める。
キャパとして考えられるmax(weights)~sum(weights)の中で最初に満たす値が最小のキャパ。
これを二分探索で求める。

キャパとして可能かどうかの判定は、以下。

1. ベルトコンベアの先から貸物の重さを足していく
2. 重さの合計がキャパを超えるかみる
3. 超えたら、一つ前までの貨物で重さをリセットし、日を進める
4. 1~3.をループしてすべての貨物を見終わったときに、所要日数がdays以内ならオッケー

時間計算量は、O(weights.length * log(max(weights) * weights.length)) なので数秒もかからない見込み。

bisect_leftを使うか迷ったが、max(weights)~sum(weights)の配列を作る方法しか思いつかなかったので、
余計に大きな配列を作らなくて良いという観点で自前の二分探索で書く方が良いかと思った。

### bisect_leftを使わない

```py
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
def can_ship(capacity: int) -> bool:
processing_days = 1
loaded = 0
for weight in weights:
if weight + loaded > capacity:
processing_days += 1
loaded = 0
loaded += weight

return processing_days <= days

maybe_first_can_ship_capacity = max(weights)
Copy link

Choose a reason for hiding this comment

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

maybe_first_can_ship_capacity という変数名からは「おそらく最初(の船?)は積載量を積載可能だろう」というニュアンスに感じました。表現したい内容とずれているように思います。 maybe_min_shippable_capacity はいかがでしょうか?これもやや微妙ではありますが…。

Copy link
Owner Author

Choose a reason for hiding this comment

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

レビューありがとうございます。
maybe_min_shippable_capacityの方が適切ですね。

left, rightより良い変数がないかはもう少し考えるべきでした。

can_ship_capacity = sum(weights)
while maybe_first_can_ship_capacity < can_ship_capacity:
mid = (maybe_first_can_ship_capacity + can_ship_capacity) // 2
if can_ship(mid):
can_ship_capacity = mid
else:
maybe_first_can_ship_capacity = mid + 1

return maybe_first_can_ship_capacity
```
Copy link

Choose a reason for hiding this comment

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

FFF?????TTTTがあって、maybe_first_can_ship_capacityは最初の?で、can_ship_capacityは最初のTでしょうか。
それならばcan_ship_capacityを返したほうが直観的に思いました。

それと変数名的にmaybe_first_can_ship_capacityが帰ってくるのは怖くなりました。
仕事を任せた人が「たぶん最小はこれです」と渡してきたら不安になるような感じでしょうか。

Copy link
Owner Author

Choose a reason for hiding this comment

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

maybe_first_can_ship_capacityは最初の?で、can_ship_capacityは最初のTでしょうか。

maybe_first_can_ship_capacityが最初のTか、Fで、can_ship_capacityが常にTになる値のイメージです。

ループの打ち切りがmaybe_first_can_ship_capacity==can_ship_capacityなので伝わるだろうと思いmaybeの方を返すようにしましたが、おっしゃる通りmaybeな値が返ってくるのは良くないですね

Copy link

@fuga-98 fuga-98 Jun 4, 2025

Choose a reason for hiding this comment

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

ループの打ち切りがmaybe_first_can_ship_capacity==can_ship_capacityなので伝わるだろう

これは伝わると思います。


### bisect_leftを使う

```py
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
def can_ship(capacity: int) -> bool:
processing_days = 1
loaded = 0
for weight in weights:
if weight + loaded > capacity:
processing_days += 1
loaded = 0
loaded += weight

return processing_days <= days

maybe_min_capacity = max(weights)
Copy link

Choose a reason for hiding this comment

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

upper_limit,lower_limitとかでしょうか

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。
limitが適切な表現ですね

maybe_max_capacity = sum(weights)
candidates= range(maybe_min_capacity, maybe_max_capacity + 1)
min_capacity_index = bisect_left(candidates, True, key=can_ship)
return candidates[min_capacity_index]
```

## step2
### 読んだ
- https://github.com/fuga-98/arai60/pull/44/files
- https://github.com/fhiyo/leetcode/pull/45/files
- https://github.com/sakupan102/arai60-practice/pull/45/files
- https://github.com/nittoco/leetcode/pull/47/files
- https://github.com/olsen-blue/Arai60/pull/44/files
- https://github.com/hroc135/leetcode/pull/42/files

### 感想
- daysが0以下は早期リターンすべきだった
- 変数名が冗長すぎるかもしれない
- rangeでリストが作られると思っていたが、実際に作られるのはstartとstop地点、ステップと長さを持ったrange objectらしい
Copy link

Choose a reason for hiding this comment

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

The advantage of the range type over a regular list or tuple is that a range object will always take the same (small) amount of memory, no matter the size of the range it represents (as it only stores the start, stop and step values, calculating individual items and subranges as needed).
https://docs.python.org/3.13/library/stdtypes.html#typesseq-range

Copy link
Owner Author

Choose a reason for hiding this comment

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

リンクの共有ありがとうございます。
根拠のurlもつけるべきでした。

- ので、自分がstep1時に考えていた「bisect_leftを使うか迷ったが、max(weights)~sum(weights)の配列を作る方法しか思いつかなかった」は杞憂だった
- 必要になった時点で初めて計算するという遅延評価というものらしい
- generatorとかiteratorも同じか
- `range(sum(weights) + 1)`を与えて、`lo=max(weights)`とすればindexではなくて、min_capacityそのものを探せて綺麗な実装だと思った

https://github.com/python/cpython/blob/9ad0c7b0f14c5fcda6bfae6692c88abb95502d38/Objects/rangeobject.c#L68
> obj->start = start;
> obj->stop = stop;
> obj->step = step;
> obj->length = length;

実際に中身を見てみた

```py
>>> start = 0
>>> end = 50000000 + 1
>>> range_object = range(start, end + 1)
>>> list_object = list(range(start, end + 1))
>>> import sys
>>> print(f"range object: {sys.getsizeof(range_object)} bytes")
range object: 48 bytes
>>> print(f"list object: {sys.getsizeof(list_object)} bytes")
list object: 400000072 bytes
```

```py
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
def can_ship(capacity: int) -> bool:
processing_days = 1
loaded = 0
for w in weights:
if w + loaded > capacity:
processing_days += 1
loaded = 0 # reset
loaded += w
return processing_days <= days

if days < 1:
raise ValueError("days must be greater than 1.")

min_capacity = bisect_left(
range(sum(weights) + 1),
True,
lo=max(weights),
key=can_ship)
return min_capacity
```

### step3
```py
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
def can_ship_within_days(capacity: int) -> bool:
task_duration_days = 1
loaded = 0

for w in weights:
if w + loaded > capacity:
task_duration_days += 1
loaded = 0 # reset
loaded += w

return task_duration_days <= days
Copy link

Choose a reason for hiding this comment

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

task_duration_daysがdaysを超えたときにreturn Falseしてもよいですね

Copy link
Owner Author

Choose a reason for hiding this comment

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

レビューありがとうございます。

これ気付けてなかったです。
weights.lengthのサイズが大きい場合などは必須で入れたいですね


min_capacity_candidates = range(sum(weights))
Copy link

Choose a reason for hiding this comment

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

答えとしてあり得る最大値は sum(weights) なので range(sum(weights+1)) の方がいいと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

レビューありがとうございます。
おっしゃる通りですね。

return bisect_left(
min_capacity_candidates,
True,
lo=max(weights),
key=can_ship_within_days
)
```