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
134 changes: 134 additions & 0 deletions 198_house-robber.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# 198. House Robber

## 1st

### ①

DPで解く解法を何となく覚えていた。漸化式から前2つだけ覚えておけば良いことがわかる。

`len(nums) == 2`のときは特別扱いしなくて良かった。

所要時間: 10:43

n: len(nums)
- 時間計算量: O(n)
- 空間計算量: O(1)

```py
class Solution:
def rob(self, nums: List[int]) -> int:
if not nums:
return 0
if len(nums) == 1:
return nums[0]
if len(nums) == 2:
return max(nums[0], nums[1])
max_money_so_far = [nums[0], max(nums[0], nums[1])]
for i in range(2, len(nums)):
prev = max_money_so_far[1]
max_money_so_far[1] = max(max_money_so_far[1], max_money_so_far[0] + nums[i])
max_money_so_far[0] = prev
return max_money_so_far[1]
```

### ②

メモ化再帰。rob_helperはmax_robbable_money_fromという名前でもいいかもしれない。

所要時間: 7:35

n: len(nums)
- 時間計算量: O(n)
- 空間計算量: O(n)

```py
class Solution:
def rob(self, nums: List[int]) -> int:
@cache
def rob_helper(i: int) -> int:
if i == len(nums) - 1:
return nums[-1]
if i == len(nums) - 2:
return max(nums[-2], nums[-1])
return max(rob_helper(i + 1), rob_helper(i + 2) + nums[i])

if not nums:
return 0
return rob_helper(0)
```

### ③

逆からメモ化再帰。こっちの方が素直か。rob_helperはmax_robbable_money_untilという名前でもいいかもしれない。

Choose a reason for hiding this comment

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

こちらが自然な気がします。再帰を使う場合はTop Downのアプローチが向いていると思います。②はBottom Upだけど再帰を使っていて見慣れなかったです。


所要時間: 1:47

n: len(nums)
- 時間計算量: O(n)
- 空間計算量: O(n)

```py
class Solution:
def rob(self, nums: List[int]) -> int:
@cache
def rob_helper(i: int) -> int:
if i == 0:
return nums[0]
if i == 1:
return max(nums[0], nums[1])
return max(rob_helper(i - 1), rob_helper(i - 2) + nums[i])

if not nums:
return 0
return rob_helper(len(nums) - 1)
```

## 2nd

### 参考

- https://discord.com/channels/1084280443945353267/1192736784354918470/1253694708719550516

maxはiterableを取れるので、①のmax_money_so_farの初期化は `max_money_so_far = [nums[0], max(nums[:1])]` で良い。

- https://discord.com/channels/1084280443945353267/1227073733844406343/1244882790638420060
- https://discord.com/channels/1084280443945353267/1201211204547383386/1223216025919553666

maxのdefaultキーワードを使って入力がから配列だったときの考慮をしている。

漸化式の初項を0, 二番目の項をnums[0]とする見方もある。

```py
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums) < 2:
return max(nums, default=0)
max_money_so_far = [0, nums[0]]
for i in range(1, len(nums)):
prev = max_money_so_far[1]
max_money_so_far[1] = max(max_money_so_far[1], max_money_so_far[0] + nums[i])
max_money_so_far[0] = prev
return max_money_so_far[1]
```

書いてみると、どちらも趣味の範囲な気がした。

- https://discord.com/channels/1084280443945353267/1200089668901937312/1217116484203970701


## 3rd

```py
class Solution:
def rob(self, nums: List[int]) -> int:
if not nums:
return 0
if len(nums) == 1:
return nums[0]
Comment on lines +126 to +127

Choose a reason for hiding this comment

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

このコードだと len(nums) == 1を特別扱いする必要はないと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

たしかに...!気づきませんでした、ありがとうございます。
でもこのコードで漸化式の初項しかない入力が来たときに、max_money_so_farに値を2つ入れるのちょっと違和感あります...特別な場合を無理やり同じ処理にまとめているというか。

max_money_so_far = [nums[0], max(nums[:2])]

Choose a reason for hiding this comment

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

素直に2変数使うほうが読みやすいかなと思もいました。自分は2周してようやく何をしているかが分かりました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

なるほど、ありがとうございます。
漸化式を配列で表したときの末尾2つ、というイメージで書いていたのであまり読みにくい発想なかったんですが、その考え方を共有できないと分かりにくくなるかもと思いました。
変数名ちょっと迷うんですが、左から順に更新するイメージでlast_max_money, second_last_max_moneyとかですかね...更新するときは

for i in range(2, len(nums)):
    max_money = max(last_max_money, second_last_max_money + nums[i])
    second_last_max_money = last_max_money
    last_max_money = max_money

みたいな。

Choose a reason for hiding this comment

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

Invariant
Initial:
max_money_so_far: [max_money[0], max_money[1]]
loop i start:
max_money_so_far: [max_money[i - 2], max_money[i - 1]]
loop i end:
max_money_so_far: [max_money[i - 1], max_money[i]]
After loop:
max_money_so_far: [max_money[n - 2], max_money[n - 1]]

Copy link
Owner Author

Choose a reason for hiding this comment

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

そうですね、そのイメージでした。

Choose a reason for hiding this comment

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

Loop invariantをコメントに入れたら、分かりやすくなりそうでしょうか。(max_moneyはどこかで定義する)

        max_money_so_far = [nums[0], max(nums[:2])] # max_money_so_far: [max_money[0], max_money[1]]
        for i in range(2, len(nums)):
            # max_money_so_far: [max_money[i - 2], max_money[i - 1]]
            prev = max_money_so_far[1]
            max_money_so_far[1] = max(max_money_so_far[1], max_money_so_far[0] + nums[i])
            max_money_so_far[0] = prev
            # max_money_so_far: [max_money[i - 1], max_money[i]]

Copy link

Choose a reason for hiding this comment

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

so_far はこの場合はそりゃそうだろう感がありませんか。
この場合の特徴は、最後を使う場合、使わない場合ですかね。

for i in range(2, len(nums)):
prev = max_money_so_far[1]
max_money_so_far[1] = max(max_money_so_far[1], max_money_so_far[0] + nums[i])
max_money_so_far[0] = prev
return max_money_so_far[1]
```