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

- あまり集中できておらず、どうやったら最後の処理がうまいことできるのか考えて手が止まってしまった
- 結局各回の最大値保持してやるのを2パターン分保持するようにすれば最低限の要件は満たすのでそれで実装
- 最初の家+3軒目以降最後除く
- 2軒目以降最後まで

の2通りの制約の中で大きい方を選ぶ


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

Choose a reason for hiding this comment

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

numsに空配列が渡される場合も考慮すると,以下のような書き方もあります

if len(nums) <= 2:
    return max(nums, default=0)

robbed_with_first = [nums[0], 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 軒目では盗めないため、 [nums[0], 0] としたほうが意味的には正しいと思います。おそらく同じ出力になると思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かにそうですね。ここは意味にこだわらないと違和感がありますね。

robbed_without_first = [0, nums[1]]
for i in range(2, len(nums)-1):
robbed_with_first.append(max(robbed_with_first[-2] + nums[i], robbed_with_first[-1]))
robbed_without_first.append(max(robbed_without_first[-2] + nums[i], robbed_without_first[-1]))
robbed_without_first.append(max(robbed_without_first[-2] + nums[-1], robbed_without_first[-1]))
return max(robbed_with_first[-1], robbed_without_first[-1])
```

### Complexity Analysis

- 時間計算量:O(n)
- 家の数に比例
- 空間計算量:O(n)
- 家の数分の配列が2つできる

## Step 2. Alternatives

- 関数に切り分けて2軒目以降の配列と最後より前の配列を処理する方法もある
- シンプルで読みやすいがパッと思いつかなかった
- 1人の作業者の目線でしか見れてない気がする
- 2人の作業者のうち大きくなりそうな方を選ぶのが良さそう
- https://github.com/tokuhirat/LeetCode/pull/36/files#diff-0ada15169c1bfbc855ba450fe39ac9bc5f80ec8f67e80356fce67b87aa72a6baR20
- index管理すればスプリットしなくてOKというお決まりの話もある
- 一軒目をループに入れる方法もある
- https://github.com/tokuhirat/LeetCode/pull/36/files#diff-0ada15169c1bfbc855ba450fe39ac9bc5f80ec8f67e80356fce67b87aa72a6baR63
- cacheも自然に選択肢に出てくるようにしたい
- https://github.com/olsen-blue/Arai60/pull/36/files#diff-ce0cd96bb02b9d0f849c47d3ecac921373d9a9834854c70c62942f51d909f929R94
- 使うならこういうことも知っておきたい

> - 結果のキャッシュは辞書なので、関数の引数は ハッシュ可能なものが必要
>
>
> - 引数のパターンが異なる場合は、別々のキャッシュエントリーになる
>
> 例えば、 f(a=1, b=2) と f(b=2, a=1) は別になる
>
- キーワードの順序によって別のキャッシュが作られることに関しては、ソートの計算時間を省略するためらしい
- https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/functools.py#L456-L473
- 自作するとこんな感じらしい分かりやすい

```python
@my_cache
def my_cache(func):
global cache
cache = dict()
def wrap(*args, **kwargs):
if args
```

Choose a reason for hiding this comment

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

正しく理解されているか記載いただいた内容からは読み取れませんでした。一度実装してみることをおすすめいたします。

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かに少し変でした。修正しました。

def my_cache(func):
    global cache
    cache = dict()
    def wrap(*args, **kwargs):
        if args in cache:
            return cache[args]
        cache[args] = func(*args, **kwargs)
        return cache[args]
    return wrap

class Solution:
    def rob(self, nums: List[int]) -> int:
        @my_cache
        def rob_range(start: int, end: int) -> int:
            rob = 0
            rob_prev = 0
            for i in range(start, end):
                rob_with_i = rob_prev + nums[i]
                rob_prev = rob
                rob = max(rob, rob_with_i)
            return rob
        return nums[0] if len(nums) == 1 else \
            max(rob_range(0, len(nums)-1), rob_range(1, len(nums)))

Choose a reason for hiding this comment

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

cache は global とするより wrap 関数内で nonlocal と書きたいです。my_cache より広いスコープである必要はないですよね。global とすると以下のようにおかしなことが起きます。

@my_cache
def f(a, b):
    return a * b

@my_cache
def g(a, b):
    return a + b

print(f(1, 2))  # 2
print(g(1, 2))  # 2

さらに、cache は dict で、ミュータブルなオブジェクトを変更しているだけなので nonlocal も不要と思います。(手元で検証する際に from functools import cache とすると名前が衝突してしまい動かなくなってしまいました。名前空間を汚すのは、global を使うデメリットの一つですね。import functools として functools.cache とするのが良いということでもあると思いますが。)

ちなみに、この解法の場合 rob_range は2回しか呼ばれず、cache は使われないですね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

nonlocalとglobalの使い分けができていませんでした。ご指摘いただきありがとうございます。



## Step 3. Final Solution

- 変数名はなるべくシンプルに

```python
class Solution:
def rob(self, nums: List[int]) -> int:
def rob_range(start: int, end: int) -> int:
rob = 0
rob_prev = 0
for i in range(start, end):
rob_with_i = rob_prev + nums[i]
rob_prev = rob
rob = max(rob, rob_with_i)
return rob
return nums[0] if len(nums) == 1 else \
Copy link

@fuga-98 fuga-98 Jul 30, 2025

Choose a reason for hiding this comment

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

ここを二行に分けるならシンプルに書いた方が見やすいと思います。

if len(nums) == 1
    return nums[0]
return max(rob_range(0, len(nums)-1), rob_range(1, len(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.

確かにそうですね。最初に一行で書いて、ちょっと長くなったから改行ということをしていましたがちゃんと見直すべきでした。

max(rob_range(0, len(nums)-1), rob_range(1, len(nums)))

Choose a reason for hiding this comment

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

-の前後はスペースを入れるのが一般的だと思います。
https://peps.python.org/pep-0008/#other-recommendations

Copy link

Choose a reason for hiding this comment

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

Pep8はどちらでも良いらしいです。
fuga-98/arai60#24 (comment)

Choose a reason for hiding this comment

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

補足いただきありがとうございます。単に自分がよく見るコードがそうだっただけみたいですね。。。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。自分も演算子の周りをどうするかは基本各自判断と記憶していて、入れたり入れなかったりですね。入れる方が良いという考えも頂戴しておきます

```
Copy link

Choose a reason for hiding this comment

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

変数名が分かりやすかったです。