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
365 changes: 365 additions & 0 deletions 121._Best_Time_to_Buy_and_Sell_Stock/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
# 121. Best Time to Buy and Sell Stock <!-- omit in toc -->

## 1. 問題

### 1.1. リンク

<https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/>

### 1.2. 問題概要 (閲覧制限のある問題の場合のみ)

## 2. 次に取り組む問題のリンク

<https://leetcode.com/problems/permutations/description/>
<!-- markdownlint-disable-file MD033 -->

## 3. ステップ1

愚直解は買う日と売る日の全探索で,与えられるストックプライスが $N$ 日分だとして $O(N^2)$ .
これより計算量が良いアルゴリズムはいろいろある.

<details>
<summary> 素直な線形時間アルゴリズム</summary>
売る日を固定したとき,買う日を全探索しなくても,売る日までの最低価格がわかれば達成できる最大利益が計算できる.
更に,ある日の前日までの最低価格が分かっているなら,その日までの最低価格はすぐにわかる;前日までの最低価格とその日の価格のうち小さいほう.

よって,各日で売る場合の最大利益が効率的に計算できるので,あとは日付順にループを回せば全体としての最大利益が線形時間でわかる.
</details>

時間計算量 $O(N)$ ・空間計算量 $O(1)$ .

```python
class Solution:
def maxProfit(self, prices: List[int]) -> int:
acc_min = float("inf")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

こちらのコメントをご参照ください。
hemispherium/LeetCode_Arai60#10 (comment)

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.

気を抜くとやってしまいます,気を付けます.

ans = 0
for price in prices:
acc_min = min(acc_min, price)
ans = max(ans, price - acc_min)
return ans

```

<details>
<summary> 分割統治による $O(N \log N)$ 時間アルゴリズム </summary>

- [アルゴリズムイントロダクション](https://www.amazon.co.jp/%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%82%A4%E3%83%B3%E3%83%88%E3%83%AD%E3%83%80%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3-%E7%AC%AC3%E7%89%88-%E7%B7%8F%E5%90%88%E7%89%88-%E4%B8%96%E7%95%8C%E6%A8%99%E6%BA%96MIT%E6%95%99%E7%A7%91%E6%9B%B8-%E3%82%B3%E3%83%AB%E3%83%A1%E3%83%B3/dp/476490408X), CLRS, の分割統治の章で見たことある.
- 階差を作って部分配列の和の最大化問題に言い換え→"真ん中"をまたぐ部分配列なら線形で最適解を計算出来て,またがない奴は部分問題の解になるから,(またぐやつ, またがない左右の部分問題) の3つに分割して統治する,という方針だったはず.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

この解法、初めて見たかもしれません。面白い解法ですね。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

私も初めてみました。
この問題を見て最大部分配列問題として捉えるのは視点の幅がすごいなと思いました。

- 思い出してみる;
1. ストックプライス列 $\text{prices}$ の長さ $N$ に対して,元問題は $$\underset{0\le i \le j < N} {\text{maximize}} \text{prices}[j]-\text{prices}[i].$$ ここで,階差数列 $\text{diffs}[i] := \text{prices}[i+1]-\text{prices}[i]\ (i=0,\dots,N-2)$ を用いると, $\text{prices}[i] = \text{prices}[0]+\sum_{k=0}^{i-1}\text{diffs}[k]\ (i=0, \dots, N-1)$ より, $$\underset{0\le i \le j < N} {\text{maximize}} \sum_{k=i}^{j-1}\text{diffs}[k]$$ と書き換えられる.すなわち,部分配列の和の最大化問題に帰着できる.
1. ある部分配列 $\text{diffs}[i:j]$ に対して,その部分配列 $\text{diffs}[k:l]$ は,以下の3通りに分類できる
1. 真ん中より左側 ( $l\le \lfloor\frac{i+j}{2}\rfloor$ )
- これは部分問題で,再帰的に解ける
2. 真ん中より右側 ( $\lfloor\frac{i+j}{2}\rfloor\le k$ )
- これも部分問題で,再帰的に解ける
3. 真ん中を含む ( $k<\lfloor\frac{i+j}{2}\rfloor< l$ )
- これは $j-i$ に対して線形時間で解ける
1. ii. の性質により,効率的に解ける. $N$ に対する時間計算量を $T(N)$ としたとき $$T(N) = 2T\left(\frac{N}{2}\right) + \Theta(N)$$ を満たすので, $T(N) = O(N\log N)$ .

</details>

時間計算量 $O(N \log N)$ ・空間計算量 $O(N)$ .

```python
class Solution:
def maxProfit(self, prices: List[int]) -> int:
diffs = [prices[i + 1] - prices[i] for i in range(len(prices) - 1)]
return self.__max_profit(diffs, 0, len(diffs))

def __max_profit(self, diffs: List[int], begin: int, end: int) -> int:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

アンダースコア 2 つから始まるメソッドは mangling の対象となり、内部用であるという意図を強く感じさせるものの、完全に private というわけではありません。通常はアンダースコア 1 つで十分だと思います。

参考までにスタイルガイドへのリンクを共有いたします。

https://peps.python.org/pep-0008/#method-names-and-instance-variables

To avoid name clashes with subclasses, use two leading underscores to invoke Python’s name mangling rules.

Python mangles these names with the class name: if class Foo has an attribute named __a, it cannot be accessed by Foo.__a. (An insistent user could still gain access by calling Foo._Foo__a.) Generally, double leading underscores should be used only to avoid name conflicts with attributes in classes designed to be subclassed.

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

Prepending a double underscore (__ aka “dunder”) to an instance variable or method effectively makes the variable or method private to its class (using name mangling); we discourage its use as it impacts readability and testability, and isn’t really private. Prefer a single underscore.

なお、このスタイルガイドは“唯一の正解”というわけではなく、数あるガイドラインの一つに過ぎません。チームによって重視される書き方や慣習も異なります。そのため、ご自身の中に基準を持ちつつも、最終的にはチームの一般的な書き方に合わせることをお勧めします。

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.

コメントと参考の提示ありがとうございます.
技術的な部分は理解しました.そういわれるとダンダーを使うことのメリットがそれらを上回るとは言えないというところで納得したので,今後はアンダースコア1つを通常の選択として運用したいと思います.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

細かい点で恐縮ですが、ダンダーはメソッド名の前後にアンダースコアが2つ付いているメソッドを指すようです。

https://docs.python.org/ja/3.14/library/dataclasses.html#module-contents

訳注:dunderはdouble underscoreの略で、メソッド名の前後にアンダースコアが2つ付いているメソッド

Copy link
Copy Markdown
Owner Author

@arahi10 arahi10 Apr 15, 2026

Choose a reason for hiding this comment

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

補足ありがとうございます。自分も気になっていたので検索していたところでした。

if begin == end:
return 0
Comment on lines +70 to +71
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

begin == endのケースってあるんだっけ?と思ったら、株価データが一つしかないときのベースケースなんですね。
この下でend - begin == 1のベースケースがあるので、そこにたどり着くことなくbegin == endになるデータタセットがあるはずと考えるとすごく納得いきました。

if end - begin == 1:
return max(0, diffs[begin])
center = (begin + end) // 2
left = self.__max_profit(diffs, begin, center)
right = self.__max_profit(diffs, center, end)
center_crossing = self.__find_center_crossing_max_profit(diffs, begin, center, end)
return max(left, right, center_crossing)

def __find_center_crossing_max_profit(self, diffs: List[int], begin: int, center: int, end: int) -> int:
assert begin < center < end

left_sum = diffs[center - 1]
left_max_sum = left_sum
for i in range(center - 2, begin - 1, -1):
left_sum += diffs[i]
left_max_sum = max(left_max_sum, left_sum)

right_sum = diffs[center]
right_max_sum = right_sum
for i in range(center + 1, end):
right_sum += diffs[i]
right_max_sum = max(right_max_sum, right_sum)

return left_max_sum + right_max_sum

```

<details>
<summary> 名前がついてる線形時間アルゴリズム</summary>

- なんか線形で解くやつに名前が付いていて,最大化問題を適宜書き換えると帰着できたはず
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これだけ丁寧に数式に落とされていてすごいと思いました。
私が数学苦手なのもあり、読み解くのに時間がかかりましたが大変勉強になりましたmm

- $\underset{0\le i < j < N} {\text{maximize}} \sum_{k=i}^{j-1}\text{diffs}[k]$<br>
$=\underset{0< j < N} {\text{maximize}} \left\\{\underset{0\le i < j} {\text{maximize}}\sum_{k=i}^{j-1}\text{diffs}[k]\right\\}$<br>
$=\underset{0< j < N} {\text{maximize}} P_{j},$ where $P_{j}:= \underset{0\le i < j } {\text{maximize}} \sum_{k=i}^{j-1}\text{diffs}[k]$.
- $P_{1} = \text{diffs}[0]$ かつ $j>1$ に対して $P_{j} = \underset{0\le i < j } {\text{maximize}} \sum_{k=i}^{j-1}\text{diffs}[k]$<br>
$= \max\left(\underset{0\le i < j-1 } {\text{maximize}} \sum_{k=i}^{j-1}\text{diffs}[k],\\; \sum_{k=j-1}^{j-1}\text{diffs}[k]\right)(i\neq j-1\text{と}i=j-1\text{に分割})$<br>
$= \max\left(\underset{0\le i < j-1 } {\text{maximize}} \sum_{k=i}^{j-1}\text{diffs}[k],\\; \text{diffs}[j-1]\right)$<br>
$= \max\left(\underset{0\le i < j-1 } {\text{maximize}} \left\\{ \sum_{k=i}^{j-2}\text{diffs}[k] + \text{diffs}[j-1]\right\\},\\; \text{diffs}[j-1]\right),(k\le j-2\text{と}k=j-1\text{に分割})$<br>
$\therefore P_{j} = \max\left(P_{j-1} + \text{diffs}[j-1],\\; \text{diffs}[j-1]\right).$
- 得られた $P_{j}$ の漸化式から, $P_{j} (j=1,\dots,N-1)$ の計算は全体で $O(N)$ で行える.
- Kadane's Algorithm.
- 実装量は少ないだろうが,この流れで思い出すのに紙とペンが必要.
- ほかの思い出し方としては,ある日 $i$ に売るときの最大利益が分かっているなら,その次の日 $i+1$ に売るときの最大利益はすぐに計算できる;日 $i$ の最大利益を達成する売買と同じ日に買うか,買う日も変えるか.
- 同じ日に買うときの利益は, $\text{diff}[i]$ を日 $i$ の最大利益に足せば求められる.
- 利益は買う日を $j$ として $\text{prices}[i+1] - \text{prices}[j] = (\text{prices}[i] - \text{prices}[j]) + (\text{prices}[i+1] - \text{prices}[i])$ で,第一項は前日の最大利益,第二項は $\text{diff}[i]$ とそれぞれ等しい.
- 違う日に買うときの最大利益は,日 $i$ の利益の最大性より,買う日を日 $i$ にするときしか達成しえない.もし日 $k<i$ があって, $$\text{prices}[i+1] - \text{prices}[k] > \text{prices}[i+1] - \text{prices}[j]$$ ならば,両辺に $\text{prices}[i] - \text{prices}[i+1]$ を足せば $$\text{prices}[i] - \text{prices}[k] > \text{prices}[i] - \text{prices}[j]$$ となるが,これは日 $i$ で売るときの右辺の最大性に反する.よってこのような日 $k < i$ は存在しないから,日 $i$ で買うとするしかない.
- 動的計画法チックな論法だから思い出しやすいだけで,説明なしに初見で理解できる気がしない.処理の説明は簡単だが,アルゴリズムの正当性の説明は普通に面倒くさい.

</details>

時間計算量 $O(N)$ ・空間計算量 $O(N)$ .

```python
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# kadane's algorithm
if len(prices) <= 1:
return 0
diffs = [prices[i + 1] - prices[i] for i in range(len(prices) - 1)]
sell_today = diffs[0]
max_prof = sell_today
Comment on lines +131 to +132
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

sell_todayが動詞っぽく感じられて、またprofと対応していない感じがしたので、対応関係があるとわかりやすいなと思いました。
local_max_prof, global_max_profなど

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.

仰る通りだと思います.動詞から始まると関数名のようにも受け取れる点からも,変数名に改善の余地がありましたね.
「profと対応していない」のようにほかの変数との兼ね合いという観点も大事だと思います.今後意識してみます.ありがとうございます.

for j in range(1, len(diffs)):
sell_today = max(sell_today + diffs[j], diffs[j])
max_prof = max(max_prof, sell_today)
return max(0, max_prof)

```

`diffs`を $N$ 日分すべて同時に記憶しておく必要はないのでジェネレータ式で空間計算量 $O(1)$ にもできる;

```python
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# kadane's algorithm
if len(prices) <= 1:
return 0
# 変更開始
diffs = (prices[i + 1] - prices[i] for i in range(len(prices) - 1))
sell_today = next(diffs)
max_prof = sell_today
for diff in diffs:
sell_today = max(sell_today + diff, diff)
# 変更終わり
max_prof = max(max_prof, sell_today)
return max(0, max_prof)

```

## 4. ステップ2

### 4.1. コードと改善するときに考えたこと

- 処理を関心で分離するために,`itertools.accumulate` を使う
- ステップ1のコードでは最大利益の計算とその日までの最低価格の計算が同じ for ループにあり,分離できると思いやってみた
- ステップ1のコードくらいの処理量だったら分けなくてもよい?と思っていたが,書いてみたら自然言語の説明により近づいていて結構好みだった.
- `itertools.accumulate`からジェネレータ(とおよそ等価なやつ, [cf](https://docs.python.org/ja/3/library/itertools.html#itertools.accumulate))でもらえるから,空間計算量のオーダーは増えない.
- 変数名の英単語を変に省略しない

```python
import itertools


class Solution:
def maxProfit(self, prices: List[int]) -> int:
acc_min_prices = itertools.accumulate(prices, func=min)
max_profit = 0
for selling, buying in zip(prices, acc_min_prices):
max_profit = max(max_profit, selling - buying)
return max_profit

```

- `prices` が空配列のときと`len(prices) == 1`のときの扱いはステップ1のコードと同じように0を返す扱いをすることにした
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

空配列は問題上想定されていないですが、想定されていない入力値にはエラーで返したいという気持ちが個人的にはあります。
状況によってどちらかがいいかは変わると思います。

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.

仰る通り,どちらかに明確な(状況に左右されない)優劣が存在しないと思いますので,状況により適切な方を選べるようになっていることが大事なのかなと考えています.

- それぞれ「売り買いできない」,「買えても売れない」場合に相当する.どの場合でも利益は当然ないので0を返すのが自然,と考えた.
- ロジックを追いやすくするためにエッジケースを早めに処理する
- `prices` が空配列のときと`len(prices) == 1`のときの扱いを `maxProfit` で明示的に扱うようにした.
- ステップ1のときは `__max_profit`の `if begin == end: ~` が陰に扱っていた
- わかりやすくするためにインデックスの細々したところをなくす
- 階差数列`price_changes`の計算に`itertools.pairwise`を使い,for each で書く.
- なるだけ広いユースケースでメソッドを使えるようにする
- `maxProfit` の `if begin == end` → `if begin >= end: ~` に変更し, (`maxProfit`側でチェック済みだけれども)残しておく
- カバレッジは減ってしまう(この点では`assert begin < end` のほうが良い)
Comment on lines +192 to +193
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

こちらも個人的には入力として想定していないものはエラーにしたい気持ちがありassert推しです。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

__find_center_crossing_max_profit の方はfloat("-inf")を返しているので、privateメソッドではfloat("-inf")を返してpublicのmax_profitのところでどう返すか調整するでも良さそうと思いました。

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.

こちらも個人的には入力として想定していないものはエラーにしたい気持ちがありassert推しです。

__max_profit はprivateなメソッドなので,よりassertで済ましてしまいやすいかとは思います(制約を破るのはユーザではなくprivateなメソッドを呼び出す実装側の人=同僚や未来の自分なので).一方,仕様追加・修正の必要に迫られたときにこの__max_profit をより広いユースケースでもなるべく使えるように実装しておくことも,それはそれでありだとは思います.こちらについても,両方見えてくのがベターなのでしょうね.

__find_center_crossing_max_profit の方はfloat("-inf")を返しているので、privateメソッドではfloat("-inf")を返してpublicのmax_profitのところでどう返すか調整するでも良さそうと思いました。

こちらの方針とも迷ったのですが,「最大利益は0以上の値を返す」という知識が max_profitにも__max_profitにも埋め込まれてしまうのを嫌って,このように意思決定したと記憶しています.

- `__find_center_crossing_max_profit` の assert をやめて`float("-inf")`を返す.
- `left_sum = 0`, `left_max_sum = float("-inf")` (`right_~`も同様)と初期化してfor文の開始インデックスを`center - 1`(`right_~`では`center`)にすれば同様の挙動をするが,エッジケースを早めに処理する意図でこちらを採用.
- なんかのPEPで1行79文字以下推奨だったはずなので多めに改行しておく
- [PEP8](https://peps.python.org/pep-0008/)でした.
- `__find_center_crossing_max_profit` で`itertools.islice`は以下の2点を理由として使わない.
- 時間計算量が $O(\text{stop})$ っぽいので([cf](https://docs.python.org/3/library/itertools.html#itertools.islice)),全体の計算量を悪化させてしまう.
- `itertools.islice` は`start, stop`に非負整数,`step` に正整数を要求する.今回の実装は`step = -1`あるいは`stop = -1`(`begin = 0` のとき)とした呼び出しでこの要求に違反する.
- `begin = 0` のとき,スライスで書いたとしても,`arr[center - 1 : -1 : -1] = []`となってしまい,今回ほしいものである`[arr[center - 1], ..., arr[1], arr[0]]` は取れない.
- こうなる理由は要調査.
- 理解では以下の挙動をするから;
1. $N=len(arr)$ としてインデックス `start`,`stop`の評価 ( $[-N, N]$ にクリップしてから負の数は $+N$ して非負にする)
2. `start`と`step`が等しい,`step`が0,あるいは, $\text{stop} - \text{start}$ と`step`が異符号であるときは空配列を返す.
3. そうでなければ指定されたインデックスの要素を取り出して配列にして返す.

```python
import itertools


class Solution:
def maxProfit(self, prices: List[int]) -> int:
if len(prices) < 2:
return 0
price_changes = [
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

changesのほうが時系列のニュアンスがあるみたいですね。
私はdiffsしか思いつかなかったのでとても良い表現だと思いました。

today - yesterday for yesterday, today in itertools.pairwise(prices)
]
return self.__max_profit(price_changes, 0, len(price_changes))

def __max_profit(self, price_changes: List[int], begin: int, end: int) -> int:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

同じmax_profitの名前でpublicなものをinterface, privateなものを実際の計算アルゴリズムとして分けた実装がとてもいいなと思いました。
pythonだとよくこういう書き方をするのでしょうね。普段使っていないので勉強になりました。

if begin >= end:
return 0
if end - begin == 1:
return max(0, price_changes[begin])
center = (begin + end) // 2
left = self.__max_profit(price_changes, begin, center)
right = self.__max_profit(price_changes, center, end)
center_crossing = self.__find_center_crossing_max_profit(
price_changes, begin, center, end
)
return max(left, right, center_crossing)

def __find_center_crossing_max_profit(
self,
price_changes: List[int],
begin: int,
center: int,
end: int,
) -> int | float:
if not begin < center < end:
return float("-inf")
left_acc_sums = itertools.accumulate(
price_changes[i] for i in range(center - 1, begin - 1, -1)
)
right_acc_sums = itertools.accumulate(
price_changes[i] for i in range(center, end)
)
return max(left_acc_sums) + max(right_acc_sums)

```

- ロジックを追いやすくするためにエッジケースを早めに処理する
- (取り除いても期待通り動くけど) `if len(prices) < 2` のif文を残しておく.
- わかりやすくするためにインデックスの細々したところをなくす
- 階差数列`price_changes`の計算に`itertools.pairwise`を使い,for each で書く.
- 変数名の英単語を変に省略しない

```python
import itertools


class Solution:
def maxProfit(self, prices: List[int]) -> int:
# kadane's algorithm
if len(prices) < 2:
return 0
today_max_profit = 0
max_profit = 0
price_changes = (
today - yesterday for yesterday, today in itertools.pairwise(prices)
)
for change in price_changes:
today_max_profit = max(today_max_profit + change, change)
max_profit = max(max_profit, today_max_profit)
return max_profit

```

### 4.2. 講師陣のコメントとして想定されること

### 4.3. 他の人のコードを読んで考えたこと

## 5. ステップ3

```python
import itertools


class Solution:
def maxProfit(self, prices: List[int]) -> int:
acc_mins = itertools.accumulate(prices, min)
max_profit = 0
for buying, selling in zip(acc_mins, prices):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

計算上問題は発生しないですが、当日までの最低価格ではなく、前日までの最低価格を使った方が直感的かと思いました。

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.

確かにそちらの方が直感的な説明と対応がとりやすいですね.
その方針でも改めて書いてみようと思います.

profit = selling - buying
max_profit = max(max_profit, profit)
return max_profit

```

```python
import itertools


class Solution:
def maxProfit(self, prices: List[int]) -> int:
if len(prices) < 2:
return 0
price_changes = [
today - yesterday for yesterday, today in itertools.pairwise(prices)
]
max_profit = self.__max_profit(price_changes, 0, len(price_changes))
return max_profit

def __max_profit(self, price_changes: List[int], begin: int, end: int) -> int:
if begin >= end:
return 0
if end - begin == 1:
return max(0, price_changes[begin])
center = (begin + end) // 2
left = self.__max_profit(price_changes, begin, center)
right = self.__max_profit(price_changes, center, end)
center_crossing = self.__center_crossing_max_profit(
price_changes, begin, center, end
)
return max(left, right, center_crossing)

def __center_crossing_max_profit(
self,
price_changes: List[int],
begin: int,
center: int,
end: int,
) -> int | float:
if not begin < center < end:
return float("-inf")
left_acc_sums = itertools.accumulate(
price_changes[i] for i in range(center - 1, begin - 1, -1)
)
right_acc_sums = itertools.accumulate(
price_changes[i] for i in range(center, end)
)
return max(left_acc_sums) + max(right_acc_sums)

```

```python
import itertools


class Solution:
def maxProfit(self, prices: List[int]) -> int:
if len(prices) < 2:
return 0
today_max_profit = 0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"その日"というニュアンスでtoday("今日")が使われているのが少し違和感持ちました。(non-nativeです;)
today→current yesterday→previousのほうが時間軸が固定されずに良い気がしました。

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.

たしかに,どちらかというとループ中における"This day" であってTodayではないですね.ちょっとLLMとも相談しながら英語のニュアンスとして適切なものを探して,ステップ4として後日作成しようと思います.ありがとうございます.

max_profit = 0
price_changes = (
today - yesterday for yesterday, today in itertools.pairwise(prices)
)
for change in price_changes:
today_max_profit = max(today_max_profit + change, change)
max_profit = max(max_profit, today_max_profit)
return max_profit

```