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

- シンプルに可能性のある値からスタートして実験して成功しなければ+1してみる
- 可能性のある値としては、最大値と合計/日数の大きい方
- 本当は+1していくよりも効率的なやり方があるような気もするが一旦これで実装

```python
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
min_capacity = max(max(weights), sum(weights) // days)

Choose a reason for hiding this comment

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

私はぱっと見だと分からず少し思考が必要だったので、単にmax(weights)という明らかに最小だと分かるものにしておくのもよいかもしれません。
ただ、可能な限り大きい値にしておけばループの回数も減るのでその辺はトレードオフになりそうです。

Choose a reason for hiding this comment

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

常にmax(weights)>= sum(weights) // daysが成立するので、いずれにせよ冗長だと思います。

daily_capacity = min_capacity
days_left = days
while True:

Choose a reason for hiding this comment

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

min_capacityはループの中で必ず1ずつ増えていくので自分ならforループにするかなと思いました

for weight in weights:
if daily_capacity < weight:
days_left -= 1
daily_capacity = min_capacity
if days_left == 0:
break
daily_capacity -= weight
if days_left > 0:
return min_capacity
days_left = days

Choose a reason for hiding this comment

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

自分はこれはforループの前の初期化のイメージなのでfor weight in weights:の前に書くかなと思いました。
そうすると12行目の代入も省けそうです

min_capacity += 1
daily_capacity = min_capacity

Choose a reason for hiding this comment

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

23行目と同様にfor weight in weights:の前に書くかなと思いました。

```

### Complexity Analysis

- 時間計算量:O(xn)
- 答えを見つけるまでにwhile文を回す回数x
- 最大いくつまで行けるかはよく分かっていなかったが重量の合計まで上げれば確実にOK
- 重量のリスト長n
- 空間計算量:O(1)
- 追加で必要な変数は3個分

## Step 2. Alternatives

- max(weights)とsum(weights)の間に答えがあると考えられる(!)のでこれを用いて二分探索する方法がある
- これがどれだけ速いのかをよく理解できていない
- 元々期待値として線形に船の容量を上げるとどうなるのか?

Choose a reason for hiding this comment

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

探索の範囲を具体的に考えてみるとどうでしょうか。例えば長さ10000の範囲だとして、線形探索では最悪何ステップかかって二分探索だと最悪何ステップになるか、という具体例を考えてみるとどれだけ速くなるかが定量的につかめるかなと思います。

- bisect_leftの引数に自分で定義した関数を渡す方法
- https://github.com/tokuhirat/LeetCode/pull/44/files#diff-06f87195f0dc2635b3c326a3257c212b916a27bd5f47afbf0368d37831b513a3R29

Choose a reason for hiding this comment

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

このような方法があるのですね、これは勉強になりました!

- 個人的にはまだ慣れていないがシンプルに書けていると感じる
- rangeをbisect_leftの探索対象のリストに入れている
- 省メモリ
- https://github.com/Fuminiton/LeetCode/pull/44/files#r2129170877
- 他にも同じような発想が多い
- https://github.com/Fuminiton/LeetCode/pull/44/files
- https://github.com/nittoco/leetcode/pull/47/files
- いつも思うがなぜ皆同じような解法になっているんだろうか?自分だけ何かを理解できていないような気分
- 取りあえず今回のような範囲を思いつきにくい場合でも二分探索を軸に考えるのは一般的と理解しておく
Copy link

@nanae772 nanae772 Sep 14, 2025

Choose a reason for hiding this comment

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

イメージとしてはどっかでTrue/Falseが入れ替わる境界があるときに二分探索が使えそうだな~という発想になるのかなと思っています。

今回のケースで言うと「days日間でcapacityの船で全ての積み荷を輸送できるか?」というブール値がcapacityを増やしていくと
False, False, ..., False, True, True, ...
と明確に「ここから下は全てFalse」「ここから上は全てTrue」という境界があるはずなので二分探索できるなという気持ちですかね。

- days=0の時の考慮
- 無限ループになってしまう
- 初めに弾いておくべき
- https://github.com/nittoco/leetcode/pull/47/files#diff-4e146417f14c744a10f851601f26cd2cb17b420ff966720e568f6f5679aa475eR79
- こういう秒数の見積もりを自然にやりたい
- https://github.com/Fuminiton/LeetCode/pull/44/files#diff-26f729e002c5c9244ef5172608184baffaf2040258f34729cff27b841079b595R18
- 二分探索での実装

```python
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
if not weights:
return 0
if days <= 0:
return -1

def canShipWithCapacity(capacity: int) -> bool:
Copy link

Choose a reason for hiding this comment

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

inner function の関数名は、 lower_snake で書くことが多いと思います。 LeetCode が指定している関数名は仕方ないと思います。

参考までにスタイルガイドへのリンクを貼ります。

https://peps.python.org/pep-0008/#function-and-variable-names

Function names should be lowercase, with words separated by underscores as necessary to improve readability.

https://google.github.io/styleguide/pyguide.html#316-naming

function_name

上記のスタイルガイドは唯一絶対のルールではなく、複数あるスタイルガイドの一つに過ぎないということを念頭に置くことをお勧めします。また、所属するチームにより何が良いとされているかは変わります。自分の中で良い書き方の基準を持ちつつ、チームの平均的な書き方で書くことをお勧めいたします。

days_left = days
daily_capacity_left = capacity
for weight in weights:
if daily_capacity_left < weight:
days_left -= 1
daily_capacity_left = capacity
if days_left == 0:
return False
daily_capacity_left -= weight
return True

left = max(weights)
right = sum(weights)
Comment on lines +81 to +82

Choose a reason for hiding this comment

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

left, rightがどのような不変条件を満たしているかを言語化してみて変数名をつけるとより分かりやすくなるのかなと思いました。
二分探索を考える際、以下の議論なども参考になるかもしれません。
KentaroJay/Leetcode#18 (comment)
potrue/leetcode#41 (comment)

while left < right:
middle = (left + right) // 2
if canShipWithCapacity(middle):
right = middle
else:
left = middle + 1
return left
```


## Step 3. Final Solution

- 自作関数をbisect_leftに渡すのを練習
- loを使わない方法としては以下がある

```python
return bisect_left(
range(max(weights), sum(weights) + 1),
True,
key=canShipWithCapacity
) + max(weights)
```


```python
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
if days <= 0:
return -1
if not weights:
Copy link

Choose a reason for hiding this comment

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

おそらくL128のmaxでエラーを出さないために入れていると思うのですが、maxにdefault引数を指定してこれは消してしまっても良いと思います。
https://docs.python.org/ja/3.13/library/functions.html#max

return 0

def canShipWithCapacity(capacity: int) -> bool:
daily_load = 0
required_days = 1
for weight in weights:
daily_load += weight
if daily_load > capacity:
required_days += 1
daily_load = weight
return required_days <= days

return bisect_left(
Copy link

Choose a reason for hiding this comment

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

面白い書き方だと思いました。
個人的にはindexという一見capacityとは対応していなさそうに見えるものを返すのは一瞬不思議に思いましたが、慣れればそうでもないのかもしれません。

sequence = range(max(weights), sum(weights) + 1)
index = bisect_left(sequence, True, key=canShipWithCapacity)
return sequence[index]

このようにしてしまう選択肢もあると思いました。

range(sum(weights) + 1),
True,
lo=max(weights),
key=canShipWithCapacity
)
```