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
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
## Step 1

- 問題文
- 非負整数の配列`prices`が与えられる。prices[i]はi日目の株価である。
- どこかの日で株を買い、それより未来の日で株を売って得られる収益の最大値を知りたい。
- 取引は1回までできる。(`prices`が単調増加する場合などで)取引しない場合は収益は0になる。
- 制約:
- 1 <= prices.length <= 10^5
- 0 <= prices[i] <= 10^4

### 実装1

- アルゴリズムの選択
- brute forceする場合は計算回数が1 + 2 + ... + (n-1) = n(n-1)/2回になり、時間計算量はO(n^2)となる。
- 今回も最適部分構造を見つけて動的計画法をやりたい。
- 何の情報を返すかが大事で、部分問題の最大収益だけ分かっても意味がない。
- 時系列に沿って考えるのが自然とみて、最終日を右に広げていくことを考える。
- なお、空売りしてから買うことにすればおそらく右から考えることもできそうだが、特にやる意味はない。
- 少なくとも、部分問題での価格の最小値は必要。逆にそれさえ分かればいけそう。
- 価格の最小値より小さい価格が来たら、買いどきの更新。それ以外は売りどきかだけ判断すれば問題は順次解ける。
Copy link

Choose a reason for hiding this comment

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

別の考え方として、「買う前の状態」「株を持っている状態」「売った状態」の3状態しかないので、それぞれの状態での最大の所持金(スタートを0とする)を考えるというのもあります。

- 動的計画法でO(n)。これ以上小さくするのは不可能だろう。
- 実装
- loopで書けば良さそう。
- 最初の日だけ例外的に処理(必ず暫定の買いどき)し、以降はループで処理するのがわかりやすい。
- 計算量
- Time: O(n)
- Space: O(1)

```python3
from typing import List


class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices:
return 0

max_profit = 0
min_price = prices[0]
for price in prices[1:]:

Choose a reason for hiding this comment

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

コピーで無駄なメモリを使うというのはそうなのですが、0 から iterate しても問題なく解けますね (問題設定上も同日に売って買うことに対する制限はなかったと思います)。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。

おっしゃるとおり、0からiterateも動作上問題ないです。

自分は、min_price = prices[0]を初期値とするなら1からiterateするし、0からiterateするならmin_price = prices = float("inf")とするのが一貫している(無駄がない?)と感じ、そう書きました。

if price < min_price:
min_price = price
continue
if price - min_price > max_profit:
max_profit = price - min_price
return max_profit
```

- ここまで8分。
- レビュー by GPT-5
- > `prices[1:]` はリストのスライスを新規作成するので、微小ながら無駄なメモリを使います。インデックスで 1 から回すか、`math.inf` 初期化で全要素を一度に処理するとより素直です。

## Step 2

- https://github.com/nanae772/leetcode-arai60/pull/36/
- 初期化やスライスに関して吟味されている。
- https://docs.python.org/ja/3/library/itertools.html#itertools.islice
- itertools.islice(...) は「選択された要素を返すイテレータを作る」ので、部分列を事前に作らない=コピーを作らない使い方になる。
- prices[1:]prices[1:] のようなリストスライスは「新しいリストを返す」ため、その部分列分のメモリ割り当てが発生する。
- [コメント集](https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.8qw2um7il4s5)
- https://discord.com/channels/1084280443945353267/1196472827457589338/1196473519689703444
- > 本質的には、
> - scanl min でその日までの最安の値。
> - zipWith (-) で利益。
> - max を取る。
- pythonで実装してみる -> [実装2](#実装2)
- Haskellでは遅延評価により各ステップで要素を一つずつしか保持しないので、入力pricesが実質一回の走査で処理され、追加メモリは定数オーダで済むとのこと。
- 関連して、itertoolsの公式ドキュメントに、Haskellに着想を得て...と書いてあり、なるほどと思いました。
- https://docs.python.org/3/library/itertools.html
- > This module implements a number of iterator building blocks inspired by constructs from APL, Haskell, and SML. Each has been recast in a form suitable for Python.

### 実装2

- Haskell風(遅延評価)
- 計算量
- Time: O(n)
- Space: O(1)

```python3
from typing import List
from itertools import accumulate


class Solution:
def maxProfit(self, prices: List[int]) -> int:
"""scanl min -> zipWith (-) -> maximum"""
if not prices:
return 0
prefix_mins = accumulate(prices, func=min) # scanl1 min
profits = (p - m for p, m in zip(prices, prefix_mins)) # zipWith (-)
return max(profits) # maximum
```

- 補足
- prefix_minsはiterator
- func引数はHaskellのscanlの第一引数と同じ。なお第二引数はHaskellと異なり明示せず、暗黙でリストの1番目を使う。
- https://docs.python.org/ja/3/library/itertools.html?ref=trap.jp#itertools.accumulate
- profitsはgenerator expressionで定義されたgenerator
- https://docs.python.org/ja/3.11/reference/expressions.html#generator-expressions
- profits[0]が必ず0になるので、デフォルティング(取引しない)はそこが担っている。(ややパズル?でもシンプル)

## Step 3

### 実装3

```python3
from typing import List

Choose a reason for hiding this comment

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

Python 3.9 以降は built-in list を使うことになっています。
https://docs.python.org/3/library/typing.html#typing.List

from itertools import islice
Copy link

@ryosuketc ryosuketc Oct 29, 2025

Choose a reason for hiding this comment

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

どのスタイルに準拠するかによりますが、たとえば Google では import itertools -> itertools.islice といった使い方をします。
https://google.github.io/styleguide/pyguide.html#22-imports
LeetCode で簡便のためこのようにインポートすることは問題ないと思いますが、スタイルによってバリエーションがあることは頭にあると良いかと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。

Use import statements for packages and modules only, not for individual types, classes, or functions.

こちら、初めて知りました。関数やクラスを直接importすると、由来がわからなくなるから好ましくないんですね。勉強になります。



class Solution:
def maxProfit(self, prices: List[int]) -> int:

Choose a reason for hiding this comment

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

List -> list というのもそうですが、ここはそもそも Sequence あたりでヒントをつけるほうがよいだろうと思います。

https://docs.python.org/3/library/typing.html#typing.List

Note that to annotate arguments, it is preferred to use an abstract collection type such as Sequence or Iterable rather than to use list or typing.List.

これは list 以外でもそうですが、引数の type hint はより abstract に、返り値はより specific にすることが多いかと思います。


…と書きはしましたがこれは LeetCode が勝手に生成している signature でしたね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。

引数の type hint はより abstract に、返り値はより specific にすることが多いかと思います。

なるほどですね。思い返すとPython documentationもそうなっていますね。

ここはそもそも Sequence あたりでヒントをつけるほうがよいだろうと思います。

なので、こういうことになるんですね。

max_profit = 0
min_price = prices[0]
for price in islice(prices, 1, None):
if price < min_price:
min_price = price
continue
potential = price - min_price
if potential > max_profit:
max_profit = potential
Comment on lines +116 to +121

Choose a reason for hiding this comment

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

min_price = min(price, min_price)
max_profix = max(price - min_price, max_profit)

でよりシンプルに書けます。if による分岐は、minmax で簡略化できるケースがある、という発想があるとよいのではないでしょうか。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。

そちら、他の参加者の方のコードでも拝見しました。

個人的には、if-continue-ifで書くと、if内の処理は高々一方しかやらない、ということが分かりやすいので好みでした。

しかしながら、maxやminはコードをコンパクトにでき、状況次第では好ましいケースもあると思うので、しっかり頭に留めておきます。

return max_profit
```