Skip to content
Open
Show file tree
Hide file tree
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
73 changes: 73 additions & 0 deletions 213/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# 213. House Robber II

- sol1.py: 全問と同じ操作を2回やれば答えが出るというパズルを解けたので満足。同じ考えを使う問題を見たことある気がする。

https://github.com/TakayaShirai/leetcode_practice/pull/35#pullrequestreview-3943529697

> ちょっととんち扱いになっているのよくないと思うので、私の考え方を書きますね。

> 極端な話、まず、2^n 通り列挙して、そこから条件を満たしているものだけ取り出して、それぞれ和を取って最大値を取ればいけますよね。この 2^n 枚の紙をバインダーに挟んで、このパターンで盗んだときに、いくらになるかを計算し、最大にならない紙を捨てて、一番大きい紙を見つける方法でやってみましょう。

> 各家の前に、泥棒が立って、バインダーが回ってきます。2^n 枚の紙の自分の担当の情報を埋めていくわけです。

> 泥棒文句言うと思うんですね。ほとんどの紙は条件を満たしていません。先に捨てておけよ。
> あと、最大になりようがないのも回ってきますね。自分は10人目なのにまだ1件も入っていない。先に捨てておけよ。

> え、でも全部捨てたら怒られますよね。じゃあ、何は捨てたらだめですか。

> これすると同じようなことが書かれている紙だけが残ります。
> これ、1件目の家で盗んだか盗んでいないか、一つ前の家で盗んだか盗んでいないか、それぞれの最大の4種類だけですね。
> 「一つ前の家で盗んだか盗んでいないか」は、自分が i 軒目の家の前にいる泥棒になったとき、自分の担当として埋めるか、埋めずに隣へ回すか、という感じで想像できました。
> 「1軒目の家で盗んだか盗んでいないか」は、最後の家の前にいる泥棒になったとき、1軒目で盗んでいるなら自分は盗めないな=自分の担当の金額は埋められないな、と思うだろう、という感じでしょうか。

https://github.com/TakayaShirai/leetcode_practice/pull/35/changes
> 言われればそうだが、どうやって思いつくかの思考回路が知りたかったので、Claude に聞いてみた。
> 1. まず円環の厄介さを特定する: 直線の House Robber は解ける。円環で何が変わるかというと、「最初と最後が隣り合っている」という制約が1つ増えただけ。
> 2. その制約を消せないか考える: 厄介な制約が1つだけなら、場合分けで消せることが多い。最初の家を「盗む」か「盗まない」かで分ければ、最後の家との関係が確定する。
> 3. 場合分けしたら既知の問題に帰着するか確認する: どちらの場合も、最初と最後のつながりが消えて直線になる。直線版はもう解けるので、それを2回使えば終わり。
> 問題として、未知のものは、「最大の盗める量」、与えられているものは、「それぞれの家の盗める量」と、House Robber でも House Robber II でも、これらは変わらない。
> 異なるのは、条件の「最初と最後が隣り合っている」という部分。これを取り除けば、全く同じ問題に帰着するから、「この条件をどうやって取り除けば良いか」を考えるのが肝だった。
> 最近意識するのを忘れていたが、「未知のものは何か」、「与えられているものは何か」、「条件は何か」を意識するのは、どんな問題を解く上でもやはり重要。

「未知のものは何か」、「与えられているものは何か」、「条件は何か」を意識する。条件を取り除く方法を考える。


https://github.com/mamo3gr/arai60/blob/213_house-robber-ii/213_house-robber-ii/memo.md

tabulationという言葉を知らなかったので、整理

```markdown
- DP(動的計画法)とは?

DPは、以下の2つの特徴を持つ問題を解くためのアルゴリズムの設計指針です。

部分構造最適性: 大きな問題の答えが、小さな問題の答えを組み合わせて作れる。

部分問題の重複: 同じ計算が何度も出てくる。

この「一度計算した小さな問題を二度と計算しないように保存しておく」という戦略全体をDPと呼びます。

- DPを実現する方法は、大きく分けて2つあります。
- Tabulation (タビュレーション) ボトムアップ DPを実現する「反復型」の手法
- Memoization (メモ化再帰) トップダウン DPを実現する「再帰型」の手法
```
sol1.pyはtabulationの解法である(DPテーブルを省略している)

https://github.com/mamo3gr/arai60/blob/213_house-robber-ii/213_house-robber-ii/step1.py
メモ化再帰の方法

https://github.com/mamo3gr/arai60/blob/213_house-robber-ii/213_house-robber-ii/step3.py
同じ解法でもインデックスだけ持つ方が効率が良いのでこれを採用しよう。

list sliceの効率
https://wiki.python.org/moin/TimeComplexity

https://github.com/naoto-iwase/leetcode/pull/41
多重代入でswap変数を避ける。これをさらにfunctools.reduceを使って関数型に置き換えている。可読性は下がるが副作用は避けられる。
https://docs.python.org/ja/3.13/library/functools.html#functools.reduce
なるほど、fold_leftと同じ関数のようだ。Pythonにも関数言語チックなこんな関数があったとは知らなかった。
itertools.isliceを使う点も勉強になる
https://docs.python.org/ja/3/library/itertools.html#itertools.islice
等価なプログラムでyieldが使われているように、配列のコピーを作成しないので、オーバーヘッドは発生しないのだろう
真似して書いてみる sol2.py

21 changes: 21 additions & 0 deletions 213/sol1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums) < 2:
return max(nums)

def rob_without_circle(nums_without_circle):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

瑣末な好みの問題ですが,rob_from_straight_lineのように「〜でない」よりも「である」を言語化する方が好みです.特に今回は直線のケースの応用として捉えているので.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

自分は「円の制約を無視する」ということを伝えたかったのだと思います。
が、確かに直線を明示した方が良いかもしれません。
(sol2.pyではご指摘と同じようにrob_linearlyにしていますね)

max_with_last = nums_without_circle[0]
max_without_last = 0

for i in range(1, len(nums_without_circle)):
next_max_without_last = max_with_last
max_with_last = max(
max_with_last, max_without_last + nums_without_circle[i]
)
max_without_last = next_max_without_last

return max(max_with_last, max_without_last)

max_value_without_first = rob_without_circle(nums[1:])
max_value_without_last = rob_without_circle(nums[:-1])
return max(max_value_without_first, max_value_without_last)
22 changes: 22 additions & 0 deletions 213/sol2.py
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

面白い書き方で勉強になりました.reduceを使うのが関数型っぽいですね(という認識であっていますかね?).

Copy link
Copy Markdown
Owner Author

@tom4649 tom4649 Apr 12, 2026

Choose a reason for hiding this comment

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

そこです。わざわざ使う必要はなさそうですが、他の人のものを真似て書いてみました。

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import functools
import itertools


class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums) < 2:
return max(nums)

def rob_linearly(sequence):
def visit_next(state: tuple[int, int], money: int) -> tuple[int, int]:
max_with_last, max_without_last = state
next_max_without_last = max_with_last
max_with_last = max(max_with_last, max_without_last + money)
return max_with_last, next_max_without_last

return max(functools.reduce(visit_next, sequence, (0, 0)))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(0, 0) を初期値とする state の正体を読み解くのと、それが引き回されていく様子をイメージするのに手こずりました。sequence も命名の抽象度が上がり分かりにくく感じました。いずれもタイプヒントを書くと多少は助けになるかもしれません。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

なるほど、採用しました。型ヒントは可読性が向上しますね。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

色々な書き方を試すというのはとても素晴らしいことだと思います。
ただ、関数型言語由来の関数を使うと、読み手にとって認知負荷が高くなる場合がありそうです。チーム内でどれくらい使われているかを確認してから使うのが良いと思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

そうですね、実際にpythonで自分がこのような書き方をすることは少なそうな気がします。勉強にはなりますが。

pythonのGuido氏も好きでなかったらしいですね。

https://jp.quora.com/%E3%81%AA%E3%81%9CGuido%E3%81%AFmap-%E3%82%84filter-reduce-%E3%81%9D%E3%81%97%E3%81%A6lambda%E3%82%92Python-3%E3%81%8B%E3%82%89%E5%8F%96%E3%82%8A%E9%99%A4%E3%81%8D%E3%81%9F%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%AE/answers/173766648?ch=10&share=e8d27008&srid=7Ndeg


return max(
rob_linearly(itertools.islice(nums, 0, len(nums) - 1)),
rob_linearly(itertools.islice(nums, 1, len(nums))),
)