-
Notifications
You must be signed in to change notification settings - Fork 0
Create 0_213. House Robber II.md #39
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,328 @@ | ||
| # 213. House Robber II | ||
| https://leetcode.com/problems/house-robber-ii/description/ | ||
|
|
||
| ## STEP1 | ||
| - 何も見ずに解いてみる | ||
|
|
||
| #### 考えたこと | ||
| - 初めの家から盗む、盗まないによって最後の家から盗めるかが変わってしまう | ||
| - 同じ条件でDPができないので、条件を変えて2回DPをする | ||
| - 上手に処理をまとめることはできそうだけど、思い浮かばなかった | ||
|
|
||
|
|
||
| 計算量 | ||
| - 時間計算量 O(N) DPを2回行うので、2Nステップ N = 100 の時に200ステップ 200ナノ秒 | ||
| - 空間計算量 O(N) 実際は2N | ||
|
|
||
| ```cpp | ||
| #include <algorithm> | ||
| #include <vector> | ||
|
|
||
| class Solution { | ||
| public: | ||
| int rob(const std::vector<int>& nums) { | ||
| if (nums.empty()) { | ||
| return 0; | ||
| } | ||
| int num_houses = nums.size(); | ||
| if (num_houses <= 2) { | ||
| return *std::max_element(nums.begin(), nums.end()); | ||
| } | ||
|
|
||
| // house_to_gain[i] = i番目までの家までを盗んだ時、得られる利得の最大値 | ||
|
|
||
| // 初めの家から盗む(代わりに最後の家から盗めない) | ||
| std::vector<int> house_to_gain(num_houses); | ||
| house_to_gain[0] = nums[0]; | ||
| house_to_gain[1] = nums[0]; | ||
| for (int i = 2; i < num_houses; i++) { | ||
| // 最後の家からは盗めない | ||
| if (i == num_houses - 1) { | ||
| house_to_gain[i] = house_to_gain[i - 1]; | ||
| continue; | ||
| } | ||
| house_to_gain[i] = std::max(house_to_gain[i - 1], | ||
| house_to_gain[i - 2] + nums[i]); | ||
| } | ||
| int candidate_1 = std::max(house_to_gain[num_houses - 1], | ||
| house_to_gain[num_houses - 2]); | ||
|
|
||
| // 初めの家から盗まない(代わりに最後の家から盗める) | ||
| std::vector<int> house_to_gain_2(num_houses); | ||
| house_to_gain_2[0] = 0; | ||
| house_to_gain_2[1] = nums[1]; | ||
| for (int i = 2; i < num_houses; i++) { | ||
| house_to_gain_2[i] = std::max(house_to_gain_2[i - 1], | ||
| house_to_gain_2[i - 2] + nums[i]); | ||
| } | ||
| int candidate_2 = std::max(house_to_gain_2[num_houses - 1], | ||
| house_to_gain_2[num_houses - 2]); | ||
|
|
||
| return std::max(candidate_1, candidate_2); | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
|
|
||
|
|
||
| ## STEP2 | ||
| ### プルリクやドキュメントを参照 | ||
| #### 問題が解けるより他人のコードを読んだりコメントするほうがよっぽど大事 | ||
| #### 参照したもの | ||
|
|
||
| - https://github.com/Fuminiton/LeetCode/pull/36/files | ||
| - https://github.com/fuga-98/arai60/pull/36/files | ||
| - https://github.com/olsen-blue/Arai60/pull/36/files | ||
| - | ||
|
|
||
|
|
||
| - ドキュメント系 | ||
|
|
||
| teachers' eye | ||
| - 使用する前に宣言する https://github.com/Fuminiton/LeetCode/pull/36/files#r2067665111 | ||
| - 脳内シミュレーションと例外処理 https://github.com/olsen-blue/Arai60/pull/36/files#r1973945286 | ||
|
|
||
|
|
||
|
|
||
| #### 感想 | ||
| - 2つ同様なものを書かないためのコツは、「初めと終わりを指定すればいい」ということか | ||
| - 1からN-1までの配列しか頭になかった。 | ||
|
|
||
| #### STEP1以外の手法と感想 | ||
| - 同じ処理をを2つ書くよりは、関数化して処理する方が見やすそう | ||
| - ただ、答えを返す関数を書く場合は、2つの場合分けの途中経過が見えなくなるという問題はある | ||
| - 途中経過が欲しい場合は、関数を返り値を配列にすれば良い | ||
| - ただ、関数の中で配列を作って返す場合、コピーが発生する可能性がある(NRVOは保証はされていない) | ||
| - 配列も欲しいなら、素直に2回書いてもいいかもしれない | ||
|
|
||
| - 配列を2つ用意する方法は、さらに2Nステップ追加することになるので、Nが大きいと避けた方がいいか。 | ||
| - ステップが増えることを除けば、2つのほぼ同じ処理をすることになり、わかりやすくて好き | ||
| - 左右の範囲を指定してDPする方法は、indexの添え字がどこなのか少しわかりにくくなりそうだ、 | ||
| - 配列のコピーは作らないので、読みやすさとトレードオフか | ||
|
|
||
| - 前問(House Robber I)と同様に、メモ化再帰をする方法や、DP配列を作らず2変数で管理する方法 | ||
| - メモリ制限が厳しいなら2変数が良い。 | ||
|
|
||
| - 総合的な今回の好みは、左右の範囲を指定してDPする方法。 | ||
|
|
||
|
|
||
| - 2つ配列を作って、共通のDPの関数を作る | ||
| ```cpp | ||
| #include <algorithm> | ||
| #include <vector> | ||
|
|
||
| class Solution { | ||
| public: | ||
| int rob(const std::vector<int>& nums) { | ||
| if (nums.empty()) { | ||
| return 0; | ||
| } | ||
| int num_houses = nums.size(); | ||
| if (num_houses <= 2) { | ||
| return *std::max_element(nums.begin(), nums.end()); | ||
| } | ||
|
|
||
| std::vector<int> nums_excluding_first(nums.begin() + 1, nums.end()); | ||
| std::vector<int> nums_excluding_last(nums.begin(), nums.end() - 1); | ||
|
|
||
| return std::max(max_gain_from_houses(nums_excluding_first), | ||
| max_gain_from_houses(nums_excluding_last)); | ||
| } | ||
| private: | ||
| int max_gain_from_houses(const std::vector<int>& nums) { | ||
| int num_houses = nums.size(); | ||
| std::vector<int> house_to_gain(num_houses); | ||
| house_to_gain[0] = nums[0]; | ||
| house_to_gain[1] = std::max(nums[0], nums[1]); | ||
| for (int i = 2; i < num_houses; i++) { | ||
| house_to_gain[i] = std::max(house_to_gain[i - 1], | ||
| house_to_gain[i - 2] + nums[i]); | ||
| } | ||
| return std::max(house_to_gain[num_houses - 1], | ||
| house_to_gain[num_houses - 2]); | ||
|
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. 上記のコメント同様、DP配列の場合、この比較は不要でした |
||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| - 配列は作らずに、DP計算をする範囲を指定する方法 | ||
| ```cpp | ||
| #include <algorithm> | ||
| #include <vector> | ||
|
|
||
| class Solution { | ||
| public: | ||
| int rob(const std::vector<int>& nums) { | ||
| if (nums.empty()) { | ||
| return 0; | ||
| } | ||
| int num_houses = nums.size(); | ||
| if (num_houses <= 2) { | ||
| return *std::max_element(nums.begin(), nums.end()); | ||
| } | ||
|
|
||
| return std::max(max_gain_in_range(nums, 0, num_houses - 2), | ||
| max_gain_in_range(nums, 1, num_houses - 1)); | ||
| } | ||
| private: | ||
| int max_gain_in_range(const std::vector<int>& nums, int start, int end) { | ||
| std::vector<int> house_to_gain(nums.size()); | ||
| house_to_gain[start] = nums[start]; | ||
| house_to_gain[start + 1] = std::max(nums[start], nums[start + 1]); | ||
| for (int i = start + 2; i <= end; i++) { | ||
| house_to_gain[i] = std::max(house_to_gain[i - 1], | ||
| house_to_gain[i - 2] + nums[i]); | ||
| } | ||
| return std::max(house_to_gain[end], | ||
| house_to_gain[end - 1]); | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| - 配列は作らずに、DPの代わりに2変数を管理する方法 | ||
| ```cpp | ||
| #include <algorithm> | ||
| #include <vector> | ||
|
|
||
| class Solution { | ||
| public: | ||
| int rob(const std::vector<int>& nums) { | ||
| if (nums.empty()) { | ||
| return 0; | ||
| } | ||
| int num_houses = nums.size(); | ||
| if (num_houses <= 2) { | ||
| return *std::max_element(nums.begin(), nums.end()); | ||
| } | ||
|
|
||
| return std::max(max_gain_in_range(nums, 0, num_houses - 2), | ||
| max_gain_in_range(nums, 1, num_houses - 1)); | ||
| } | ||
| private: | ||
| int max_gain_in_range(const std::vector<int>& nums, int start, int end) { | ||
| int max_two_before = nums[start]; | ||
| int max_one_before = std::max(nums[start], nums[start + 1]); | ||
| for (int i = start + 2; i <= end; i++) { | ||
| int temp = max_one_before; | ||
| max_one_before = std::max(max_one_before, max_two_before + nums[i]); | ||
| max_two_before = temp; | ||
| } | ||
| return std::max(max_two_before, max_one_before); | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
|
|
||
| - pythonでメモ化再帰をデコレータを書いている方がいらっしゃるので、自分もトライしてみる | ||
| - 高階関数("関数を受け取って関数を返す"関数)なのか、C++には なさそうだ | ||
|
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. C++ は operator () の定義されたクラスを作る方法で同等のことができます。
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. コメントありがとうございます。()演算子をオーバーライドすることで、関数のようにコードが見えるオブジェクトを作ることができ、この関数オブジェクトを受け取る関数クラスを作ることで、高階関数を再現できると理解しました。 #include <iostream>
// 元の関数オブジェクト(加算)
class Func {
public:
int operator()(int a, int b) {
return a + b;
}
};
// デコレータクラス(ログを追加)
class LoggingDecorator {
public:
LoggingDecorator(Func add) : f_(add) {}
int operator()(int a, int b) {
std::cout << "引数: " << a << ", " << b << endl;
int result = f_(a, b);
std::cout << "結果: " << result << endl;
return result;
}
private:
Func f_; // Func型の関数オブジェクトを保持
};
int main() {
Func add; // 元の関数オブジェクト
LoggingDecorator decorated_func(add); // デコレータでラップ
int sum = decorated_func(3, 5); // ログ付きで実行
return 0;
}ここから再帰関数をメモ化する応用はまだ理解が追いついておらず難しかったので、2周目の宿題にしようと思います |
||
| - 関数を受け取ると、関数の引数も情報として受け取れるのね | ||
| - "args"や"kargs"を引数にとれるのが便利だけど、具体的に指定することもできるはず | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def rob(self, nums: list[int]) -> int: | ||
| if len(nums) <= 2: | ||
| return max(nums, default = 0) | ||
|
|
||
| def max_gain_cache(max_gain): | ||
| cache = {} | ||
| # cacheに引数のtupleに対応する値があれば返し、ないならその関数の値の辞書のページを作り値を返す | ||
| def wrapper(begin ,end): | ||
| pair = (begin, end) | ||
| if (pair in cache): | ||
| return cache[pair] | ||
| cache[pair] = max_gain(begin, end) | ||
| return cache[pair] | ||
| return wrapper | ||
|
|
||
| @max_gain_cache | ||
| def max_gain(begin: int, end: int)-> int: | ||
| length = end - begin + 1 | ||
| if length == 1: | ||
| return nums[begin] | ||
| if length == 2: | ||
| return max(nums[begin], nums[begin + 1]) | ||
| return max(max_gain(begin, end - 1), | ||
| max_gain(begin, end - 2) + nums[end]) | ||
|
|
||
| return max(max_gain(0, len(nums) - 2), | ||
| max_gain(1, len(nums) - 1)) | ||
|
|
||
| ``` | ||
| - これだと、仮にキーワード引数と通常の引数の指定が入り乱れていても対応できるはず | ||
| - ただ、argsやkargsn代わりに、max_gainの引数を具体的に指定すると、max_gainを修飾するためだけのデコレータという感じになってあまり意味がないなと気づいた | ||
|
|
||
|
|
||
| - これが素直な気がする | ||
|
|
||
| ```python | ||
| def my_cache(func): | ||
| cache = {} | ||
| # cacheに引数のtupleに対応する値があれば返し、ないならその関数の値の辞書のページを作り値を返す | ||
| def wrapper(*args): | ||
| if args in cache: | ||
| return cache[args] | ||
| cache[args] = func(*args) | ||
| return cache[args] | ||
| return wrapper | ||
|
|
||
| class Solution: | ||
| def rob(self, nums: list[int]) -> int: | ||
| if len(nums) <= 1: | ||
| return max(nums, default = 0) | ||
|
|
||
| @my_cache | ||
| def max_gain(begin: int, end: int)-> int: | ||
| length = end - begin + 1 | ||
| if length == 1: | ||
| return nums[begin] | ||
| if length == 2: | ||
| return max(nums[begin], nums[begin + 1]) | ||
| return max(max_gain(begin, end - 1), | ||
| max_gain(begin, end - 2) + nums[end]) | ||
|
|
||
| return max(max_gain(0, len(nums) - 2), | ||
| max_gain(1, len(nums) - 1)) | ||
| ``` | ||
|
|
||
|
|
||
| ## STEP3 | ||
| ### 3回ミスなく書く | ||
| - 配列は作らずに、DP計算をする範囲を指定する方法 | ||
|
|
||
| ```cpp | ||
| #include <algorithm> | ||
| #include <vector> | ||
|
|
||
| class Solution { | ||
| public: | ||
| int rob(const std::vector<int>& nums) { | ||
| int num_houses = nums.size(); | ||
| if (num_houses == 0) { | ||
| return 0; | ||
| } | ||
| if (num_houses <= 2) { | ||
|
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. <= 3 でもいいですね。 |
||
| return *std::max_element(nums.begin(), nums.end()); | ||
| } | ||
|
|
||
| return std::max(max_gain_in_range(nums, 0, num_houses - 2), | ||
| max_gain_in_range(nums, 1, num_houses - 1)); | ||
| } | ||
|
|
||
| private: | ||
| int max_gain_in_range(const std::vector<int>& nums, int start, int end) { | ||
| std::vector<int> house_to_gain(nums.size()); | ||
|
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. サイズは nums.size()-1 か end-start+1 の方がいいのではないでしょうか? |
||
| house_to_gain[start] = nums[start]; | ||
| house_to_gain[start + 1] = std::max(nums[start], nums[start + 1]); | ||
| for (int i = start + 2; i <= end; i++) { | ||
| house_to_gain[i] = std::max(house_to_gain[i - 1], | ||
| house_to_gain[i - 2] + nums[i]); | ||
| } | ||
| return std::max(house_to_gain[end], house_to_gain[end - 1]); | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| 6分,6分,5分で3回Accept | ||
|
|
||
| #### 2周目の宿題 | ||
| - lru_cacheを実装する(pythonでも) | ||
|
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 comment
The reason will be displayed to describe this comment to others. Learn more.
DPは「あるindexまでの得られる利得の最大値」のため、house_to_gain.back()がそのまま最大値となり、ここのstd::maxは不要でした