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
70 changes: 70 additions & 0 deletions Python3/198. House Robber.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
## Step 1. Initial Solution

- 最初に思いついたのは最後に強盗に入った家とそこまでの累計額をリストに入れていく方法
- 2つ前と3つ前のうち累計額の大きい方を選んで今の家の額と足す
- エッジケース(入力1個や2個)を考慮できていなかったのでエラー
- 候補をどんどんリストに入れていって消さないで保持していたらメモリ制限に引っかかるケースがあったので、n個の候補だけ保持するように書き換えた
- もっと言えば3つ前までしか保持しなくてよいが一旦これで提出

```python
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums) <= 2:
return max(nums)
Comment on lines +12 to +13

Choose a reason for hiding this comment

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

問題の制約上は問題ないですが,numsが空だった場合どうしますか?

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件という状態はおかしいので、ここではValueErrorを返します。
問題設定の状況によってはmax(nums, default=0)とでもしておけば良さそうです。

total_robbed_at_house = nums[:2]
total_robbed_at_house.append(nums[0] + nums[2])
for i in range(3, len(nums)):
total_robbed_at_house.append(max(total_robbed_at_house[i-3:i-1]) + nums[i])
Copy link

Choose a reason for hiding this comment

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

max() に渡す前にスライスを使い、部分 list のコピーを作っている点が、処理が定数倍重くなりそうだなと感じました。ただ、 Python のようにもともと重い言語で定数倍の速度差を気にするのも微妙だと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

そうですね。少し気になりましたが、分けて書くほどでもないのかなと思ってしまいました

return max(total_robbed_at_house[-2:])
```

### Complexity Analysis

- 時間計算量:O(n)
- len(nums)のループが1回分
- 空間計算量:O(n)
- len(nums)個のリスト

## Step 2. Alternatives

- その時点で強盗した場合の合計額としなかった場合の合計額を保持する方法
- https://github.com/tokuhirat/LeetCode/pull/35/files#diff-b1642025ab2067fabcdc6478da099c2a1970c91a2662b6643d92b263fd1dbd1bR6
- シンプルで分かりやすい
- 自分はここまで抽象化して考えられていなかった気がする
- 具体例に囚われずに答えを導く方法を考えたい
- どこで強盗したかという情報がなくてもできる
- https://github.com/olsen-blue/Arai60/pull/35/files#diff-3cad6c6001234a922d7e9a0b5da82cd9db4dc34edcb80f9dc139f5e9bc09ced4R6
- 何を保持するのかの選択肢はいろいろ考えてから実装に入るのがよさそう
- ストーリーを作るのも面白い
- 再帰も選択肢の一つ
- キャッシュ化・メモ化などの工夫が可能
- メモ化の場合はスレッドセーフかどうかも気にしたい
- https://github.com/olsen-blue/Arai60/pull/35/files#diff-3cad6c6001234a922d7e9a0b5da82cd9db4dc34edcb80f9dc139f5e9bc09ced4R26

```python
class Solution:
def rob(self, nums: List[int]) -> int:
@cache
def max_robbed_to(house: int) -> int:
if house == 0:
return nums[0]
if house == 1:
return max(nums[:2])
return max(max_robbed_to(house-2) + nums[house], max_robbed_to(house-1))
return max_robbed_to(len(nums)-1)
```


## Step 3. Final Solution

- DPで処理

```python
class Solution:
def rob(self, nums: List[int]) -> int:
max_rob = [nums[0]]
Copy link

Choose a reason for hiding this comment

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

最後 2 個のみ保持すれば十分で、空間計算量 O(1) になります。

max_rob.append(max(nums[:2]))
for i in range(2, len(nums)):
max_rob.append(max(max_rob[-2] + nums[i], max_rob[-1]))

Choose a reason for hiding this comment

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

space O(1) で解けますよというのもありますね。
space O(N) で解くのであれば append していってもいいんですが、DP に使う配列は事前に len(nums) で定義しておいて、max_rob[i] = ...max_rob[i -1]...max_rob[i - 2] のような感じでアクセスしたほうが、それまでの要素との関係性で max_rob[i] を定義していることがわかりやすいかなと感じます。

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かに先に作ってしまう方がよく見る気がしますね。C言語とかでリスト長を指定する流れからそっちがスタンダードになっているだけなのかなと思ったりしているのですが、その書き方に慣れている人が多いならそっちの方が可読性は上がりそうですね

return max_rob[-1]
```