diff --git a/213_house_robber_ii_medium/README.md b/213_house_robber_ii_medium/README.md new file mode 100644 index 0000000..9ddfe6a --- /dev/null +++ b/213_house_robber_ii_medium/README.md @@ -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の漸化式をコメントに書きました + - `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/) diff --git a/213_house_robber_ii_medium/step1.py b/213_house_robber_ii_medium/step1.py new file mode 100644 index 0000000..1b8beac --- /dev/null +++ b/213_house_robber_ii_medium/step1.py @@ -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: + 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)): + 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 diff --git a/213_house_robber_ii_medium/step2.py b/213_house_robber_ii_medium/step2.py new file mode 100644 index 0000000..88771fd --- /dev/null +++ b/213_house_robber_ii_medium/step2.py @@ -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 diff --git a/213_house_robber_ii_medium/step3.py b/213_house_robber_ii_medium/step3.py new file mode 100644 index 0000000..7f6129f --- /dev/null +++ b/213_house_robber_ii_medium/step3.py @@ -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