-
Notifications
You must be signed in to change notification settings - Fork 0
213. House Robber II #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
|
|
| 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): | ||
| 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) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 面白い書き方で勉強になりました.reduceを使うのが関数型っぽいですね(という認識であっていますかね?).
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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))) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. なるほど、採用しました。型ヒントは可読性が向上しますね。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 色々な書き方を試すというのはとても素晴らしいことだと思います。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. そうですね、実際にpythonで自分がこのような書き方をすることは少なそうな気がします。勉強にはなりますが。 pythonのGuido氏も好きでなかったらしいですね。 |
||
|
|
||
| return max( | ||
| rob_linearly(itertools.islice(nums, 0, len(nums) - 1)), | ||
| rob_linearly(itertools.islice(nums, 1, len(nums))), | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
瑣末な好みの問題ですが,
rob_from_straight_lineのように「〜でない」よりも「である」を言語化する方が好みです.特に今回は直線のケースの応用として捉えているので.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
自分は「円の制約を無視する」ということを伝えたかったのだと思います。
が、確かに直線を明示した方が良いかもしれません。
(sol2.pyではご指摘と同じようにrob_linearlyにしていますね)