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
38 changes: 38 additions & 0 deletions 213_house_robber_ii_medium/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 問題へのリンク
[House Robber II - LeetCode](https://leetcode.com/problems/house-robber-ii/description/)

# 言語
Python

# 問題の概要
この問題は、円形の家のリストが与えられたときに、隣接する家を盗むことができない条件の下で、最大の金額を盗む方法を見つけることです。
家が一列に並んでいる場合の最大金額を求める問題[House Robber - LeetCode](https://leetcode.com/problems/house-robber/description/)と似ていますが、円形の配置により、最初と最後の家は隣接しています。つまり、最初の家と最後の家の両方を盗むことはできないという制約が追加されています。



# 自分の解法
以下の二つの場合に分けて考えます。円形の制約から、求める答えは以下の二つのケースのうちいずれかになります。
1. 最初の家を盗まない場合(最後の家は盗んでもよい)
2. 最後の家を盗まない場合(最初の家は盗んでもよい)

そして、それぞれのケースに対して、通常の「House Robber」の問題を動的計画法で解くことで、最大金額を求めます。動的計画法の際には、二つの変数の値だけを逐次更新していくことで、空間計算量を`O(1)`に抑えます。


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

## step2
- `rob_houses_linear`をprivateな関数`_rob_houses_linear`に変更しました
- 変数の更新の際に使う変数`tmp`を`prev_tmp`に変更しました
- `tmp`という変数が一般に良くないというのはわかりつつも、このような一時的に値を保持して数行以内で使われるような変数ではむしろわかりやすいのでは無いかと思っています。



## step3
- dpの漸化式をコメントに書きました
Copy link

Choose a reason for hiding this comment

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

単に dp で、dynamic programming だとはあまり通じない気がします。
たとえば、Chrome のソースコードで dp で調べた結果です。
https://source.chromium.org/search?q=%5Cbdp%5Cb&sq=

あと、最近の LLM、プロダクションのコードにしてくれと頼むときれいにしてくれます。

Copy link
Owner Author

Choose a reason for hiding this comment

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

レビューありがとうございます。
dp は余り浸透していない、いわば競プロの方言なのですね、気をつけます。

プロダクションのコードにしてくれと頼む

いつもLLMにコードレビューは頼んでいたのですが、その指示の出し方はしたこと無かったので参考になります。ありがとうございます。

- `dp[i] = max(dp[i-1], dp[i-2] + nums[i])`
- `dp[i]`は`i`番目の家まで考えたときの最大金額を表します。

# 次に解く問題の予告
- [ ] [Evaluate Division - LeetCode](https://leetcode.com/problems/evaluate-division/description/)
- [ ] [Subsets - LeetCode](https://leetcode.com/problems/subsets/)
38 changes: 38 additions & 0 deletions 213_house_robber_ii_medium/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#
# @lc app=leetcode id=213 lang=python3
#
# [213] House Robber II
#


# @lc code=start
class Solution:
def rob(self, nums: list[int]) -> int:
if not nums:
return 0
elif len(nums) == 1:
Copy link

Choose a reason for hiding this comment

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

直前に return 0 があるため、 if 文にしてしまってもよいと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

レビューありがとうございます。
そうですね、early return したときに以降のif文を if にするのか elifにするのか、いつも少し迷っているのですが、 if の方がわかりやすいのならそちらに統一しようと思います。

return nums[0]

max_rob_amount_excluding_first = self.rob_houses_linear(nums[1:])
Copy link

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.

たしかにおっしゃる通りですね。心がけていきます。

max_rob_amount_excluding_last = self.rob_houses_linear(nums[: len(nums) - 1])
return max(max_rob_amount_excluding_first, max_rob_amount_excluding_last)

def rob_houses_linear(self, nums: list[int]) -> int:
"""
solve house robber problem when houses are arranged linear (not in a circle)
"""
if not nums:
return 0
elif len(nums) == 1:
return nums[0]

previous_rob_amount = nums[0]
current_rob_amount = max(previous_rob_amount, nums[1])
for i in range(2, len(nums)):
tmp = current_rob_amount
current_rob_amount = max(current_rob_amount, previous_rob_amount + nums[i])
previous_rob_amount = tmp
return max(previous_rob_amount, current_rob_amount)


# @lc code=end
42 changes: 42 additions & 0 deletions 213_house_robber_ii_medium/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#
# @lc app=leetcode id=213 lang=python3
#
# [213] House Robber II
#


# @lc code=start
class Solution:
def rob(self, nums: list[int]) -> int:
"""
solve house robber problem when houses are arranged in a circle
"""
if not nums:
return 0
elif len(nums) == 1:
return nums[0]

max_rob_amount_excluding_first = self._rob_houses_linear(nums[1:])
max_rob_amount_excluding_last = self._rob_houses_linear(nums[: len(nums) - 1])
return max(max_rob_amount_excluding_first, max_rob_amount_excluding_last)

def _rob_houses_linear(self, nums: list[int]) -> int:
"""
solve house robber problem when houses are arranged linear (not in a circle)
"""
if not nums:
return 0
elif len(nums) == 1:
return nums[0]

previous_rob_amount = nums[0]
current_rob_amount = max(previous_rob_amount, nums[1])
for i in range(2, len(nums)):
prev_tmp = current_rob_amount
current_rob_amount = max(current_rob_amount, previous_rob_amount + nums[i])
previous_rob_amount = prev_tmp

return current_rob_amount


# @lc code=end
39 changes: 39 additions & 0 deletions 213_house_robber_ii_medium/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#
# @lc app=leetcode id=213 lang=python3
#
# [213] House Robber II
#


# @lc code=start
class Solution:
def rob(self, nums: list[int]) -> int:
if not nums:
return 0
elif len(nums) <= 2:
return max(nums)

rob_amount_excluing_first = self._rob_houses_linear(nums[1:])
rob_amount_excluing_last = self._rob_houses_linear(nums[: len(nums) - 1])

max_rob_amount = max(rob_amount_excluing_first, rob_amount_excluing_last)
return max_rob_amount

def _rob_houses_linear(self, nums: list[int]) -> int:
if not nums:
return 0

# dp[i]: the maximum robbed amount from first i houses; 0, ..., i-1
# dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])
# use 1-dimensional dynamic programming for efficient memory,
previous_max_rob_amount = nums[0]
max_rob_amount = max(previous_max_rob_amount, nums[1])
for num in nums[2:]:
tmp = max_rob_amount
max_rob_amount = max(max_rob_amount, previous_max_rob_amount + num)
previous_max_rob_amount = tmp

return max_rob_amount


# @lc code=end