From 393b84431d33cc8abec8e784d6c2804b8ba2b956 Mon Sep 17 00:00:00 2001 From: t9a Date: Fri, 28 Nov 2025 16:34:09 +0900 Subject: [PATCH] solve: 213.House Robber II --- src/bin/step1.rs | 95 +++++++++++++++++++++++++++++++++++++++++++---- src/bin/step1a.rs | 83 +++++++++++++++++++++++++++++++++++++++++ src/bin/step2.rs | 55 ++++++++++++++++++++++----- src/bin/step3.rs | 52 ++++++++++++++++++++++---- 4 files changed, 261 insertions(+), 24 deletions(-) create mode 100644 src/bin/step1a.rs diff --git a/src/bin/step1.rs b/src/bin/step1.rs index d640da2..7ea641d 100644 --- a/src/bin/step1.rs +++ b/src/bin/step1.rs @@ -8,26 +8,107 @@ // 正解したら終わり /* + 問題の理解 + - 家毎に置いてある金額を表す配列numsが与えられる。家から盗める金額の最大値を答えとして返す。 + 制約として隣接する家からは金を盗めない。 + 家は円形に並んでいるのでnums[0],nums[nums.len() - 1]は隣接していることになる。 + 何がわからなかったか - - + - 家が円形だとnums[0]とnums[nums.len() - 1]が隣接しているのでこのケースをどうやって検知して、除外すればよいか分からなかった。 何を考えて解いていたか - - + - まず問題の制約内で考えられるエッジケースを含んだテストケースを考える。 + - nums.len()==3のときある家から盗むと他のすべての家は隣接しているので最大値を返せば良い + - 直前の問題と同じ解き方をすると何が問題になるか。 + - nums[i - 1].max(nums[i] + nums[i - 2]) + 累積和の情報しか引き継がないので、その累積和に含まれる金がもともとどの家にあったのかの情報が失われている。 + 時間切れなので解答を見る。 - 想定ユースケース - - + 解法の理解 + - 他の人の解答を見て理解する + https://github.com/nanae772/leetcode-arai60/pull/35/files + https://github.com/h1rosaka/arai60/pull/38/files + https://github.com/docto-rin/leetcode/pull/41/files + 以下の考え方で解けると理解した。 + - 最初の家nums[0]から探索するときは最後の家nums[nums.len() - 1]は探索対象から外す。 + - 最初の家nums[0]を探索しないときは最後の家nums[nums.len() - 1]は探索対象に含める。 + 実装してみる。 正解してから気づいたこと - - + - 解法を見ると問題をいかにシンプルに捉えられるかがポイントだったと思った。 + 解法をみて自然に思いついた実装以外(空間計算量O(1))も練習のために実装する。step1a.rs +*/ + +use house_robber::Robber; + +/* + Solution::rob内でRobber::robとできないようスコープ切るためにmodを利用している。 */ +mod house_robber { + use std::collections::HashMap; + pub struct Robber {} + + impl Robber { + pub fn collect_max_amount(targets: &[i32]) -> i32 { + let mut amount_cache: HashMap = HashMap::new(); + Self::rob((targets.len() - 1) as isize, targets, &mut amount_cache) + } + + fn rob(i: isize, targets: &[i32], amount_cache: &mut HashMap) -> i32 { + if i < 0 { + return 0; + } + + if let Some(amount_cache) = amount_cache.get(&i) { + return *amount_cache; + } + + let amount = Self::rob(i - 1, targets, amount_cache) + .max(Self::rob(i - 2, targets, amount_cache) + targets[i as usize]); + amount_cache.insert(i, amount); + amount + } + } +} pub struct Solution {} -impl Solution {} +impl Solution { + pub fn rob(nums: Vec) -> i32 { + if nums.is_empty() { + return 0; + } + + match nums.len() { + 1 => return nums[0], + 2 => return nums[0].max(nums[1]), + 3 => return nums[0].max(nums[1]).max(nums[2]), + _ => (), + }; + + let with_first_targets = &nums[0..nums.len() - 1]; + let with_out_first_targets = &nums[1..nums.len()]; + + Robber::collect_max_amount(with_first_targets) + .max(Robber::collect_max_amount(with_out_first_targets)) + } +} #[cfg(test)] mod tests { use super::*; #[test] - fn step1_test() {} + fn step1_test() { + assert_eq!(Solution::rob(vec![1, 2, 3, 1]), 4); + assert_eq!(Solution::rob(vec![2, 3, 2]), 3); + assert_eq!(Solution::rob(vec![1, 2, 3]), 3); + assert_eq!(Solution::rob(vec![6, 2, 3, 4, 6]), 10); + assert_eq!(Solution::rob(vec![6, 6, 3, 4, 6]), 12); + assert_eq!(Solution::rob(vec![6, 6, 3, 4, 6, 7, 8]), 20); + + assert_eq!(Solution::rob(vec![1]), 1); + assert_eq!(Solution::rob(vec![2, 1]), 2); + + assert_eq!(Solution::rob(vec![]), 0); + } } diff --git a/src/bin/step1a.rs b/src/bin/step1a.rs new file mode 100644 index 0000000..3774362 --- /dev/null +++ b/src/bin/step1a.rs @@ -0,0 +1,83 @@ +// Step1a +// 目的: 別の解法を練習する。空間計算量がO(1)となる解法を実装する。 + +// 方法 +// 5分考えてわからなかったら答えをみる +// 答えを見て理解したと思ったら全部消して答えを隠して書く +// 5分筆が止まったらもう一回みて全部消す +// 正解したら終わり + +/* + 問題の理解 + - 家毎に置いてある金額を表す配列numsが与えられる。家から盗める金額の最大値を答えとして返す。 + 制約として隣接する家からは金を盗めない。 + 家は円形に並んでいるのでnums[0],nums[nums.len() - 1]は隣接していることになる。 + + 解法について + - 前回の問題(198.House Robber)のレビューで提案してもらった解法で解いてみた。 + 存在しない家(nums[-2],nums[-1])の金を0として仮定している部分が考え方として番兵に近い気がする。 + https://github.com/t9a-dev/LeetCode_arai60/pull/35#discussion_r2564647801 + 自分でこの解法を思いつくまでの距離は大分ある気がするものの、何をしているのかは理解できている。 + + 何を考えて解いていたか + 以下の二通りのケースでそれぞれの最大値を求めて、最後に大きい方を確認して返す。 + - 最初の家[0]を含み最後の家を除く場合(0..nums.len()-1) + - 最初の家[0]を含まず最後の家を含む場合(1..nums.len()) + + 正解してから気づいたこと + - 最大値を求めるループのコードが重複しているのでまとめられる。 + - テストコードで検知できたが、nums.len() == 1のケースをチェックしないと想定通り動かない。 + コーディング試験で行われるようなホワイトボード上でのコーディングだとテストコードによるテスト実行はできないので、配列の境界周りのロジックは大分注意が必要だと思った。 +*/ + +pub struct Solution {} +impl Solution { + pub fn rob(nums: Vec) -> i32 { + if nums.is_empty() { + return 0; + }; + if nums.len() == 1 { + return nums[0]; + } + + let mut two_before_max = 0; + let mut one_before_max = 0; + for i in 0..nums.len() - 1 { + let current_max = one_before_max.max(two_before_max + nums[i]); + two_before_max = one_before_max; + one_before_max = current_max; + } + let with_first_max = one_before_max; + + two_before_max = 0; + one_before_max = 0; + for i in 1..nums.len() { + let current_max = one_before_max.max(two_before_max + nums[i]); + two_before_max = one_before_max; + one_before_max = current_max; + } + let with_out_first_max = one_before_max; + + with_first_max.max(with_out_first_max) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step1a_test() { + assert_eq!(Solution::rob(vec![1, 2, 3, 1]), 4); + assert_eq!(Solution::rob(vec![2, 3, 2]), 3); + assert_eq!(Solution::rob(vec![1, 2, 3]), 3); + assert_eq!(Solution::rob(vec![6, 2, 3, 4, 6]), 10); + assert_eq!(Solution::rob(vec![6, 6, 3, 4, 6]), 12); + assert_eq!(Solution::rob(vec![6, 6, 3, 4, 6, 7, 8]), 20); + + assert_eq!(Solution::rob(vec![1]), 1); + assert_eq!(Solution::rob(vec![2, 1]), 2); + + assert_eq!(Solution::rob(vec![]), 0); + } +} diff --git a/src/bin/step2.rs b/src/bin/step2.rs index e92520d..88412cb 100644 --- a/src/bin/step2.rs +++ b/src/bin/step2.rs @@ -12,26 +12,61 @@ // 改善する時に考えたこと /* - 講師陣はどのようなコメントを残すだろうか? - - - 他の人のコードを読んで考えたこと - - - - 他の想定ユースケース - - + - Vecのような可変長配列の一部分を別のところへ渡す時にコピーが行われないかはだいぶ気にするようになってきた。 + Pythonだとスライスでコピーが発生する仕様らしいので覚えておく必要があると思った。 + https://github.com/Kaichi-Irie/leetcode-python/pull/6#discussion_r2171435011 + 同じ指摘を見つけたのでPythonにおけるスライスの暗黙的なコピーはよくある落とし穴っぽいと思った。 + https://github.com/ryosuketc/leetcode_arai60/pull/49#discussion_r2217263665 改善する時に考えたこと - - + - 重複しているループのコードをまとめる。 */ pub struct Solution {} -impl Solution {} +impl Solution { + pub fn rob(nums: Vec) -> i32 { + if nums.is_empty() { + return 0; + } + if nums.len() == 1 { + return nums[0]; + } + + let collect_max_amount = |targets: &[i32]| { + let mut two_before_max = 0; + let mut one_before_max = 0; + for target in targets { + let current_max = one_before_max.max(two_before_max + *target); + two_before_max = one_before_max; + one_before_max = current_max; + } + one_before_max + }; + + let with_first_max_amount = collect_max_amount(&nums[0..nums.len() - 1]); + let with_out_first_max_amount = collect_max_amount(&nums[1..nums.len()]); + + with_first_max_amount.max(with_out_first_max_amount) + } +} #[cfg(test)] mod tests { use super::*; #[test] - fn step2_test() {} + fn step2_test() { + assert_eq!(Solution::rob(vec![1, 2, 3, 1]), 4); + assert_eq!(Solution::rob(vec![2, 3, 2]), 3); + assert_eq!(Solution::rob(vec![1, 2, 3]), 3); + assert_eq!(Solution::rob(vec![6, 2, 3, 4, 6]), 10); + assert_eq!(Solution::rob(vec![6, 6, 3, 4, 6]), 12); + assert_eq!(Solution::rob(vec![6, 6, 3, 4, 6, 7, 8]), 20); + + assert_eq!(Solution::rob(vec![1]), 1); + assert_eq!(Solution::rob(vec![2, 1]), 2); + + assert_eq!(Solution::rob(vec![]), 0); + } } diff --git a/src/bin/step3.rs b/src/bin/step3.rs index 0af0a4a..6fef263 100644 --- a/src/bin/step3.rs +++ b/src/bin/step3.rs @@ -9,23 +9,61 @@ // 作れないデータ構造があった場合は別途自作すること /* - 時間計算量: - 空間計算量: + n = nums.len() + 時間計算量: O(n) + 空間計算量: O(1) */ /* - 1回目: 分秒 - 2回目: 分秒 - 3回目: 分秒 + 1回目: 3分20秒 + 2回目: 3分55秒 + 3回目: 3分05秒 */ pub struct Solution {} -impl Solution {} +impl Solution { + pub fn rob(nums: Vec) -> i32 { + if nums.is_empty() { + return 0; + } + if nums.len() == 1 { + return nums[0]; + } + + let collect_max_amount = |targets: &[i32]| { + let mut two_before_max = 0; + let mut one_before_max = 0; + for target in targets { + let current_max = one_before_max.max(two_before_max + *target); + two_before_max = one_before_max; + one_before_max = one_before_max.max(current_max); + } + one_before_max + }; + + let with_first_max_amount = collect_max_amount(&nums[0..nums.len() - 1]); + let with_out_first_max_amount = collect_max_amount(&nums[1..nums.len()]); + + with_first_max_amount.max(with_out_first_max_amount) + } +} #[cfg(test)] mod tests { use super::*; #[test] - fn step3_test() {} + fn step3_test() { + assert_eq!(Solution::rob(vec![1, 2, 3, 1]), 4); + assert_eq!(Solution::rob(vec![2, 3, 2]), 3); + assert_eq!(Solution::rob(vec![1, 2, 3]), 3); + assert_eq!(Solution::rob(vec![6, 2, 3, 4, 6]), 10); + assert_eq!(Solution::rob(vec![6, 6, 3, 4, 6]), 12); + assert_eq!(Solution::rob(vec![6, 6, 3, 4, 6, 7, 8]), 20); + + assert_eq!(Solution::rob(vec![1]), 1); + assert_eq!(Solution::rob(vec![2, 1]), 2); + + assert_eq!(Solution::rob(vec![]), 0); + } }