-
Notifications
You must be signed in to change notification settings - Fork 0
198. House Robber #33
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,5 @@ | ||
| start_new_problem.sh | ||
| main.go | ||
| go.mod | ||
| go.sum | ||
| *.go |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| 問題: https://leetcode.com/problems/house-robber/description/ | ||
|
|
||
| ### Step 1 | ||
| - 紙に書いてアルゴリズムを考える | ||
| - 考えたテストケース: [1], [1,1], [1,1,1] | ||
| - nums[i]が尻になる配列での最大値 = | ||
| max(nums[i-2]が尻になる配列での最大値 + nums[i], nums[i-1]が尻になる配列での最大値) | ||
| と思った | ||
| - しかし、入力[2,1,1,2]でWA | ||
| - 2個飛ばしの場合を想定できていなかった | ||
| - よく考えたらテストケースが少なすぎる。手抜きしない | ||
| - テストケースを考える時にパターンをすべて網羅できているかよく考えたほうがいい | ||
|
|
||
| - 以下でWA | ||
| ```Go | ||
| func rob(nums []int) int { | ||
| if len(nums) == 1 { | ||
| return nums[0] | ||
| } | ||
| if len(nums) == 2 { | ||
| return max(nums[0], nums[1]) | ||
| } | ||
| maxTwoBefore := nums[0] | ||
| maxOneBefore := nums[1] | ||
| for i := 2; i < len(nums); i++ { | ||
| currentMax := max(maxTwoBefore+nums[i], maxOneBefore) | ||
| maxOneBefore, maxTwoBefore = currentMax, maxOneBefore | ||
| } | ||
| return maxOneBefore | ||
| } | ||
| ``` | ||
|
|
||
| - 修正後 | ||
| - 最初の3つの条件分岐をループにまとめる方法もあるが、 | ||
| 個人的には最初に特殊ケースを弾いた方が直感的にわかりやすかった | ||
| - n = len(nums)として、時間計算量はO(n), 空間計算量はO(1) | ||
| ```Go | ||
| func rob(nums []int) int { | ||
| if len(nums) == 1 { | ||
| return nums[0] | ||
| } | ||
| if len(nums) == 2 { | ||
| return max(nums[0], nums[1]) | ||
| } | ||
| if len(nums) == 3 { | ||
| return max(nums[0]+nums[2], nums[1]) | ||
| } | ||
| maxThreeBefore := nums[0] | ||
| maxTwoBefore := nums[1] | ||
| maxOneBefore := max(nums[0]+nums[2], nums[1]) | ||
| for i := 3; i < len(nums); i++ { | ||
| n := nums[i] | ||
| currentMax := max(maxThreeBefore+n, maxTwoBefore+n, maxOneBefore) | ||
| maxThreeBefore, maxTwoBefore, maxOneBefore = maxTwoBefore, maxOneBefore, currentMax | ||
| } | ||
| return maxOneBefore | ||
| } | ||
| ``` | ||
|
|
||
| ### Step 2 | ||
|
|
||
| #### 2a | ||
| - step1の修正 | ||
| - 他の方のコードを見てstep1のように3つ先まで参照する必要はないことに気づいた | ||
| - numsの長さ1,2,3の場合もループの中に入れた | ||
| - 書いてみたら思ったよりすっきりになった | ||
|
|
||
| ```Go | ||
| func rob(nums []int) int { | ||
| twoBefore := 0 | ||
| oneBefore := 0 | ||
| for i, n := range nums { | ||
| if i == 0 { | ||
| oneBefore = n | ||
| continue | ||
| } | ||
| if i == 1 { | ||
| twoBefore, oneBefore = oneBefore, max(oneBefore, n) | ||
|
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. tmpTwoBefore := twoBeforeみたいに値を一時的に記憶するための変数が必要になると勘違いしていました。単に以下のように2行に分けて全く問題なかったですね。ありがとうございます twoBefore = oneBefore
oneBefore = max(oneBefore, n) |
||
| continue | ||
| } | ||
| currentMax := max(twoBefore+n, oneBefore) | ||
| twoBefore, oneBefore = oneBefore, currentMax | ||
|
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. maxtwoBeforeや、twoBeforeなどの変数名から、「2つ前を最後に取った場合の最大値」「もし配列が2つ前までだった場合の最大値」などの細かい違いを読み取るのは難しいですね。 |
||
| } | ||
| return oneBefore | ||
| } | ||
| ``` | ||
|
|
||
| #### 2b | ||
| - メモ付き再帰 | ||
| - 時間・空間計算量はともにO(n) | ||
|
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. たまに再帰上限について呟いておくといいかもしれません。 |
||
| - ??メモがないとO(2^n)時間になる?? | ||
|
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. フィボナッチになりそうですね。黄金比の n 乗なので 1.6^n くらいです。
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. memo がないとしましょう。 robHelper(100) と呼ぶと、robHelper(99) と robHelper(98) が呼ばれて、再帰的に木ができあがりますね。一番下の葉は robHelper(0) と robHelper(1) です。 robHelper(0) と robHelper(1) は、かかった計算コストの請求書を呼び出した人たちに提出します。呼び出した人たちは請求書をホチキスで止めて呼び出し元に送ります。 最後、請求書の束ができあがりますね。請求書は何枚あって、ホチキスは何回止められましたか。 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. もう調べられているかもしれませんが、念の為。 1.6というのは、上のφのことですね。
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. すみません、返信遅くなりました @oda 図示した時に二分木に見えることに囚われてしまい、bill(100) = bill(99) + bill(98)に気づくのに時間がかかりました @TORUS0818 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. はい。stapler(3)は2でしょうか。まあ、ホチキスの数は、請求書-1ですね。 |
||
| - ↑ここ自信ないです!! | ||
| - 紙に書いていくと高さ2nの二分木が出来上がりそうな感じになった | ||
|
|
||
| ```Go | ||
| func rob(nums []int) int { | ||
| memo := make(map[int]int, len(nums)) | ||
|
|
||
| var robHelper func(tailIndex int) int | ||
| robHelper = func(tailIndex int) int { | ||
| if v, found := memo[tailIndex]; found { | ||
| return v | ||
| } | ||
| if tailIndex == 0 { | ||
| return nums[0] | ||
| } | ||
| if tailIndex == 1 { | ||
| return max(nums[0], nums[1]) | ||
| } | ||
| twoBefore := robHelper(tailIndex - 2) | ||
| memo[tailIndex-2] = twoBefore | ||
| oneBefore := robHelper(tailIndex - 1) | ||
| memo[tailIndex-1] = oneBefore | ||
| return max(twoBefore+nums[tailIndex], oneBefore) | ||
| } | ||
|
|
||
| return robHelper(len(nums) - 1) | ||
| } | ||
| ``` | ||
|
|
||
| #### 2c | ||
| - 末尾要素を盗んだか盗まなかったかの2パターンの値を保持する方法 | ||
| - 参考: https://github.com/TORUS0818/leetcode/pull/37/files#diff-83c62c4ad09009d4bd113ed1c717a697821ba57360e7b4fc2cfcdc8848add9a3R139 | ||
|
|
||
| ```Go | ||
| func rob(nums []int) int { | ||
| skippedLast := 0 | ||
| robbedLast := nums[0] | ||
| for i := 1; i < len(nums); i++ { | ||
| skippedLast, robbedLast = max(skippedLast, robbedLast), skippedLast+nums[i] | ||
| } | ||
| return max(skippedLast, robbedLast) | ||
| } | ||
| ``` | ||
|
|
||
| ### Step 3 | ||
| ```Go | ||
| func rob(nums []int) int { | ||
| skippedLast := 0 | ||
| robbedLast := nums[0] | ||
| for i := 1; i < len(nums); i++ { | ||
| skippedLast, robbedLast = max(skippedLast, robbedLast), skippedLast+nums[i] | ||
| } | ||
| return max(skippedLast, robbedLast) | ||
| } | ||
| ``` | ||
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.
手下を各々の家の前に立たせて伝言をさせたら、自分の前を盗む場合と盗まない場合の話をすると思うんですよね。
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.
なるほどです。ちなみにodaさんはよく例えを使われますがそれは人に説明をするために実世界の例を作り出されていますか?それとも普段のコーディングのときから何かに例えていらっしゃるんですか?
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.
喩えていないですね。ただ、普段から、もうちょっと抽象的なものに人格を感じているので、あだ名をつけているくらいの感じです。