diff --git a/src/bin/step1.rs b/src/bin/step1.rs index d640da2..01a3ce7 100644 --- a/src/bin/step1.rs +++ b/src/bin/step1.rs @@ -8,26 +8,84 @@ // 正解したら終わり /* + 問題の理解 + - 整数配列pricesが与えられる。prices[i]は株価を表している。株の売買を行って得られる最大利益を解として返す。 + 利益が発生しなければ0を返す。売買するかを決められるので損失がでるような売買は行わない。 + 制約 + - 一度に保有できる株式は最大1株のみ + - 同じ日に複数回の売買は可能 + 何がわからなかったか - - + - 特になし 何を考えて解いていたか - - + - prices=[7, 1, 5, 3, 6, 4]の時 + - day=1の時買い、day=2の時売る。 profit=5-1=4 + - day=3の時買い、day=4の時売る。 profit=6-3=3 + max_profit=4+3=7 + - prices=[1, 2, 3, 4, 5]の時 + - day=0の時買い、day=4の時売る。 profit=5-1=4 + max_profit=4 + - prices=[7, 6, 4, 3, 2, 1]の時 + - どの時点で買っても利益が発生しないので、max_profit=0 + + - prices=[1,2,3,4,5]のときはいきなり5-1で答えを求めなくても、5-4-3-2-1と答えを求めていけば大丈夫そう。 + 今持っている株prices[i]よりも安い株を見つけたら、 + - 売買候補株を更新する 売買はスキップ + 今持っている株よりも高い株を見つけたら + - 利益を計算して通算利益を更新 + - 今持っている株を購入した株(prices[i + 1])で更新する。 - 想定ユースケース - - + Leet Code採点システムは一発でAcceptedになった。 + しかし、手元のテストケースを通らないコード(比較の方向を間違えた)を一度書いてしまったので、ホワイトボードでのコーディングだとアウトだなと思った。 正解してから気づいたこと - - + - 売買候補株を保持しておく変数名がもう少し適切なものがありそう。min_priceはpricesの中の最小を常に指すわけではないので少し忌避感がある。 + - 前回の問題のレビューで提案のあったsplit_firstによる分割を行うように改善する。 + - 再帰による実装も練習のためにしておくと良さそう。前回の問題(121. Best Time to Buy and Sell Stock)では入力のサイズが大きく、スタック深さがそのまま入力のサイズとなるので忌避感があった。 + 実装前に制約上の入力サイズでスタックサイズ上限が7MBと仮定した場合に1スタックフレームあたりに詰めるサイズを見積もってみる。 + 入力の制約 prices.length <= 3 * (10 ^ 4) = 30_000 + スタックサイズ上限7MBとすると、7 * 1024 * 1024 / 30_000 = 約244Byte となり、1スタックフレームあたり244Byteまでならスタックオーバーフローとならない。 + 余裕そうなのでstep1a.rsで再帰による実装を練習する。 */ pub struct Solution {} -impl Solution {} +impl Solution { + pub fn max_profit(prices: Vec) -> i32 { + let Some(mut candidate_stock) = prices.get(0) else { + return 0; + }; + let mut max_profit = 0; + + for price in &prices[1..] { + if price < candidate_stock { + candidate_stock = price; + continue; + } + + max_profit += price - candidate_stock; + candidate_stock = price; + } + + max_profit + } +} #[cfg(test)] mod tests { use super::*; #[test] - fn step1_test() {} + fn step1_test() { + assert_eq!(Solution::max_profit(vec![7, 1, 5, 3, 6, 4]), 7); + assert_eq!(Solution::max_profit(vec![1, 2, 3, 4, 5]), 4); + assert_eq!(Solution::max_profit(vec![7, 6, 4, 3, 1]), 0); + + assert_eq!(Solution::max_profit(vec![2, 2, 4, 8]), 6); + assert_eq!(Solution::max_profit(vec![2, 2, 4, 1]), 2); + + assert_eq!(Solution::max_profit(vec![1, 5]), 4); + assert_eq!(Solution::max_profit(vec![1]), 0); + assert_eq!(Solution::max_profit(vec![]), 0); + } } diff --git a/src/bin/step1a.rs b/src/bin/step1a.rs new file mode 100644 index 0000000..4753133 --- /dev/null +++ b/src/bin/step1a.rs @@ -0,0 +1,83 @@ +// Step1a +// 目的: 練習のために再帰による実装を行う + +/* + 問題の理解 + - 整数配列pricesが与えられる。prices[i]は株価を表している。株の売買を行って得られる最大利益を解として返す。 + 利益が発生しなければ0を返す。売買するかを決められるので損失がでるような売買は行わない。 + 制約 + - 一度に保有できる株式は最大1株のみ + - 同じ日に複数回の売買は可能 + + 何がわからなかったか + - 特になし + + 何を考えて解いていたか + - prices=[7, 1, 5, 3, 6, 4]の時 + - day=1の時買い、day=2の時売る。 profit=5-1=4 + - day=3の時買い、day=4の時売る。 profit=6-3=3 + max_profit=4+3=7 + - prices=[1, 2, 3, 4, 5]の時 + - day=0の時買い、day=4の時売る。 profit=5-1=4 + max_profit=4 + - prices=[7, 6, 4, 3, 2, 1]の時 + - どの時点で買っても利益が発生しないので、max_profit=0 + + 再帰関数の設計 + - 基本ケース + - 売買対象がなければ0を返す。 + - 再帰ケース + - 持っている株よりも売買候補の株が安ければ、所持する株を安い株に更新して日付を一日進めて再帰に入る。 + - 持っている株を売って利益を計算する。次の日付の売買を行うために再帰に入る。 + - 計算した利益を全て合算して返す。 + + step1で見積もった空間計算量(スタックオーバーフローしないか) + 入力の制約 prices.length <= 3 * (10 ^ 4) = 30_000 + スタックサイズ上限7MBとすると、7 * 1024 * 1024 / 30_000 = 約244Byte となり、1スタックフレームあたり244Byteまでならスタックオーバーフローとならない。 + 余裕そうなのでstep1a.rsで再帰による実装を練習する。 +*/ + +pub struct Solution {} +impl Solution { + pub fn max_profit(prices: Vec) -> i32 { + let Some((initial_holding_price, remaining_prices)) = prices.split_first() else { + return 0; + }; + + Self::make_max_profit(remaining_prices, 0, *initial_holding_price) + } + + fn make_max_profit(prices: &[i32], day: usize, holding_price: i32) -> i32 { + let Some(price) = prices.get(day) else { + return 0; + }; + + if *price < holding_price { + return Self::make_max_profit(prices, day + 1, *price); + } + + let current_profit = *price - holding_price; + let future_profits = Self::make_max_profit(prices, day + 1, *price); + + current_profit + future_profits + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step1a_test() { + assert_eq!(Solution::max_profit(vec![7, 1, 5, 3, 6, 4]), 7); + assert_eq!(Solution::max_profit(vec![1, 2, 3, 4, 5]), 4); + assert_eq!(Solution::max_profit(vec![7, 6, 4, 3, 1]), 0); + + assert_eq!(Solution::max_profit(vec![2, 2, 4, 8]), 6); + assert_eq!(Solution::max_profit(vec![2, 2, 4, 1]), 2); + + assert_eq!(Solution::max_profit(vec![1, 5]), 4); + assert_eq!(Solution::max_profit(vec![1]), 0); + assert_eq!(Solution::max_profit(vec![]), 0); + } +} diff --git a/src/bin/step2.rs b/src/bin/step2.rs index e92520d..b39dfe2 100644 --- a/src/bin/step2.rs +++ b/src/bin/step2.rs @@ -12,26 +12,54 @@ // 改善する時に考えたこと /* - 講師陣はどのようなコメントを残すだろうか? - - - 他の人のコードを読んで考えたこと - - - - 他の想定ユースケース - - + - 今回は特に思うところはなかった。 改善する時に考えたこと - - + - max_profitよりも通算利益っぽいtotal_profitの方が適切だと感じた + - 前回の問題のレビューで提案を受けたsplit_firstによる分割で書いてみた。 + https://github.com/t9a-dev/LeetCode_arai60/pull/37#discussion_r2575562337 + remaining(残りの)という語彙も増えたし、すっきり書けていて良いと思う。 + - price - holding_priceはprofit変数に入れた方が丁寧な気がしたが、一度しか使わないのとtotal_profitに加算していることから過剰かなと思って止めた。 */ pub struct Solution {} -impl Solution {} +impl Solution { + pub fn max_profit(prices: Vec) -> i32 { + let Some((mut holding_price, remaining_prices)) = prices.split_first() else { + return 0; + }; + let mut total_profit = 0; + + for price in remaining_prices { + if price < holding_price { + holding_price = price; + continue; + } + + total_profit += price - holding_price; + holding_price = price; + } + + total_profit + } +} #[cfg(test)] mod tests { use super::*; #[test] - fn step2_test() {} + fn step2_test() { + assert_eq!(Solution::max_profit(vec![7, 1, 5, 3, 6, 4]), 7); + assert_eq!(Solution::max_profit(vec![1, 2, 3, 4, 5]), 4); + assert_eq!(Solution::max_profit(vec![7, 6, 4, 3, 1]), 0); + + assert_eq!(Solution::max_profit(vec![2, 2, 4, 8]), 6); + assert_eq!(Solution::max_profit(vec![2, 2, 4, 1]), 2); + + assert_eq!(Solution::max_profit(vec![1, 5]), 4); + assert_eq!(Solution::max_profit(vec![1]), 0); + assert_eq!(Solution::max_profit(vec![]), 0); + } } diff --git a/src/bin/step3.rs b/src/bin/step3.rs index 0af0a4a..4aba870 100644 --- a/src/bin/step3.rs +++ b/src/bin/step3.rs @@ -9,23 +9,54 @@ // 作れないデータ構造があった場合は別途自作すること /* - 時間計算量: - 空間計算量: + n = prices.len() + 時間計算量: O(n) + 空間計算量: O(1) */ /* - 1回目: 分秒 - 2回目: 分秒 - 3回目: 分秒 + 1回目: 1分42秒 + 2回目: 1分46秒 + 3回目: 1分40秒 */ pub struct Solution {} -impl Solution {} +impl Solution { + pub fn max_profit(prices: Vec) -> i32 { + let Some((mut holding_price, remaining_prices)) = prices.split_first() else { + return 0; + }; + let mut total_profit = 0; + + for price in remaining_prices { + if price < holding_price { + holding_price = price; + continue; + } + + total_profit += price - holding_price; + holding_price = price; + } + + total_profit + } +} #[cfg(test)] mod tests { use super::*; #[test] - fn step3_test() {} + fn step3_test() { + assert_eq!(Solution::max_profit(vec![7, 1, 5, 3, 6, 4]), 7); + assert_eq!(Solution::max_profit(vec![1, 2, 3, 4, 5]), 4); + assert_eq!(Solution::max_profit(vec![7, 6, 4, 3, 1]), 0); + + assert_eq!(Solution::max_profit(vec![2, 2, 4, 8]), 6); + assert_eq!(Solution::max_profit(vec![2, 2, 4, 1]), 2); + + assert_eq!(Solution::max_profit(vec![1, 5]), 4); + assert_eq!(Solution::max_profit(vec![1]), 0); + assert_eq!(Solution::max_profit(vec![]), 0); + } }