-
Notifications
You must be signed in to change notification settings - Fork 0
122. Best Time to Buy and Sell Stock II #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
skypenguins
wants to merge
1
commit into
main
Choose a base branch
from
leetcode/arai60/problem-122
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| # 122. Best Time to Buy and Sell Stock II | ||
| - 問題: https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/ | ||
| - 言語: Python | ||
|
|
||
| ## Step1 | ||
| ### 方針 | ||
| - `121. Best Time to Buy and Sell Stock` と異なり、複数回株を売買することで `profit` を最大化する。一度に保持できる株は1個だけ | ||
| - 1回しか売買しない場合は、121の方法で良い | ||
| - 手作業でやるならどうやるかを考えていたら15分経過 | ||
| - 売却したタイミングでそれまでの最安値をリセットするのは思いついた | ||
|
|
||
| ## WA | ||
| ```py | ||
| class Solution: | ||
| def maxProfit(self, prices: List[int]) -> int: | ||
| cheapest_price = float("inf") | ||
| total_profit = 0 | ||
| profit = 0 | ||
|
|
||
| for i, price in enumerate(prices): | ||
| cheapest_price = min(cheapest_price, price) | ||
| profit = max(profit, price - cheapest_price) | ||
| if profit > 0: | ||
| total_profit += profit | ||
| cheapest_price = float("inf") | ||
| profit = 0 | ||
|
|
||
| return total_profit | ||
| ``` | ||
| - `profit` は必ず正の値になるので、 `if profit > 0` は無意味 | ||
| - 利益が少しでもプラスになった瞬間に確定させてリセットしてしまう | ||
|
|
||
| ## 正答 | ||
| - 以下はすべて同じ「上昇区間の差分をすべて拾う」というロジックを異なる形で表現している | ||
|
|
||
| ### 方針1 | ||
| - 利益がプラスになってもすぐには確定せず、「次の日の価格が下がる(=ピークを迎えた)」か「配列の最後に到達した」タイミングで初めて利益を確定 | ||
| ```py | ||
| class Solution: | ||
| def maxProfit(self, prices: List[int]) -> int: | ||
| cheapest_price = float("inf") | ||
| total_profit = 0 | ||
| profit = 0 | ||
|
|
||
| for i, price in enumerate(prices): | ||
| cheapest_price = min(cheapest_price, price) | ||
| profit = max(profit, price - cheapest_price) | ||
| # ピーク(次が値下がり)か最終日でだけ利益を確定させる | ||
| if i == len(prices) - 1 or prices[i + 1] < price: | ||
| total_profit += profit | ||
| cheapest_price = float("inf") | ||
| profit = 0 | ||
|
|
||
| return total_profit | ||
| ``` | ||
| ### 方針2 | ||
| - 連続する日の値上がり分をすべて足す | ||
| - 貪欲法(Greedy) | ||
| - 「安く買って高く売る」を細かく繰り返しても、一気に買って一気に売っても、利益の合計は数学的に同じになる、という発想 | ||
| ```py | ||
| class Solution: | ||
| def maxProfit(self, prices: List[int]) -> int: | ||
| return sum(max(0, prices[i] - prices[i - 1]) for i in range(1, len(prices))) | ||
| ``` | ||
| - 時間計算量:$O(n)$ | ||
| - 空間計算量:$O(1)$ | ||
|
|
||
| ### 方針3 | ||
| - 谷と山(Valley-Peak)を明示的に探す | ||
| ```py | ||
| class Solution: | ||
| def maxProfit(self, prices: List[int]) -> int: | ||
| i, n = 0, len(prices) | ||
| total_profit = 0 | ||
|
|
||
| while i < n - 1: | ||
| # 谷を探す | ||
| while i < n - 1 and prices[i] >= prices[i + 1]: | ||
| i += 1 | ||
| valley = prices[i] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 好みの問題かもしれませんが、 個人的には 軽く調べたところ、金融文脈だと |
||
|
|
||
| # 山を探す | ||
| while i < n - 1 and prices[i] <= prices[i + 1]: | ||
| i += 1 | ||
| peak = prices[i] | ||
|
|
||
| total_profit += peak - valley | ||
|
|
||
| return total_profit | ||
| ``` | ||
| - 時間計算量:$O(n)$ | ||
| - 外側・内側のループを合計しても i の移動回数は高々 n 回 | ||
| - 空間計算量:$O(1)$ | ||
|
|
||
| ### 方針4 | ||
| - DPで考える | ||
| - 「株を持っている状態」と「持っていない状態」の2つを管理する方法 | ||
| - あり得る売買履歴の世界線を大量に走らせて、各状態ごとに一番利益の出る世界線だけ残す | ||
| ```py | ||
| class Solution: | ||
| def maxProfit(self, prices: List[int]) -> int: | ||
| hold = -prices[0] # 株を保有している場合の最大利益 | ||
| not_hold = 0 # 株を保有していない場合の最大利益 | ||
|
|
||
| for price in prices[1:]: | ||
| hold, not_hold = max(hold, not_hold - price), max(not_hold, hold + price) | ||
|
|
||
| return not_hold | ||
| ``` | ||
| - タプルで代入し、右側から計算する | ||
| - 以下のイメージ? | ||
| ``` | ||
| 今日の hold = | ||
| max( | ||
| 昨日すでに持っていた世界線, | ||
| 昨日持っていなくて、今日買う世界線 | ||
| ) | ||
|
|
||
| 今日の not_hold = | ||
| max( | ||
| 昨日すでに持っていなかった世界線, | ||
| 昨日持っていて、今日売る世界線 | ||
| ) | ||
| ``` | ||
| - 最後は必ず `not_hold`(株を売り切った状態)が答え。株を持ったまま終わっても得はしないから。 | ||
| - 「取引回数がk回まで」「売却に手数料がかかる」といった拡張がしやすい | ||
| - 時間計算量:$O(n)$ | ||
| - 空間計算量:$O(1)$ | ||
|
|
||
| ## Step2 | ||
| - 典型コメント集: https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.h4s44httjcso | ||
|
|
||
| - https://github.com/goto-untrapped/Arai60/pull/59 | ||
| - > 毎日できることは、株を持っているか、お金を持っているかの2択なので、未来が見える人になったとして、どちらがいいかを考えればいいのです。 | ||
| - src: https://github.com/goto-untrapped/Arai60/pull/59/changes#r1782748689 | ||
|
|
||
| - https://github.com/nittoco/leetcode/pull/44 | ||
| - 過去と現在の価格を比較し、過去の株を買ったことにしておいて、安い場合は利益、高い場合は損失とする | ||
|
|
||
| ## Step3 | ||
| ### 読みやすく書き直したコード | ||
| #### 方針4 | ||
| ```py | ||
| class Solution: | ||
| def maxProfit(self, prices: List[int]) -> int: | ||
| hold = -prices[0] | ||
| not_hold = 0 | ||
|
|
||
| for price in prices[1:]: | ||
| hold, not_hold = max(hold, not_hold - price), max(not_hold, hold + price) | ||
|
|
||
| return not_hold | ||
| ``` | ||
| - 所要時間: | ||
| - 1回目: 1:52 | ||
| - 2回目: 1:12 | ||
| - 3回目: 1:39 | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
18 行目と 26 行目の profit = 0 は、 22 行目の代入文で上書きされるため、不要だと思いました。