Skip to content
Open
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
94 changes: 94 additions & 0 deletions 213/213.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
## 何も見ずに解いてみる

最初の家に入ったか入っていないかで場合分けしないといけなさそう。

```cpp
class Solution {
public:
int rob(vector<int>& nums) {
int prev_max_with_first_broken = 0;
int current_max_with_first_broken = 0;
int prev_max_with_first_unbroken = 0;
int current_max_with_first_unbroken = 0;
if (nums.size() == 1) {
return nums[0];
}
for (int i; i < nums.size(); ++i) {
int num = nums[i];
if (i > 0) {
int next_max_with_first_unbroken = std::max(current_max_with_first_unbroken, prev_max_with_first_unbroken + num);
prev_max_with_first_unbroken = current_max_with_first_unbroken;
current_max_with_first_unbroken = next_max_with_first_unbroken;
}
if (i < nums.size() - 1) {
int next_max_with_first_broken = std::max(current_max_with_first_broken, prev_max_with_first_broken + num);
prev_max_with_first_broken = current_max_with_first_broken;
current_max_with_first_broken = next_max_with_first_broken;
}
}
return std::max(current_max_with_first_broken, current_max_with_first_unbroken);
}
};
```

こっちの方がわかりやすいかも。有効な家の入り方は、最初の家に入らない場合と最後の家に入らない場合の二つに分けられる。(任意の隣り合う二つの家でも良いですが)  
https://cpprefjp.github.io/reference/span/span.html
書いているときは気づきませんでしたが空の配列が渡された時に`nums.begin() + 1`とか`nums.end() - 1`でバグりそうなので`nums.size() == 0`の時の場合分けが欲しいかもしれませんね。

```cpp
class Solution {

Choose a reason for hiding this comment

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

LGTM です。個人的にはこれが一番好きですね。

public:
int rob(vector<int>& nums) {
if (nums.size() == 1) {
return nums[0];
}
std::span<int> span_without_first(nums.begin() + 1, nums.end());
std::span<int> span_without_last(nums.begin(), nums.end() - 1);
return std::max(rob_line(span_without_last), rob_line(span_without_first));
}
private:
int rob_line(std::span<int>& nums) {
Copy link

Choose a reason for hiding this comment

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

std::span は軽量な軽量なデータ参照のため、値渡ししてしまってよいと思います。

#include <iostream>
#include <span>

int main()
{
    std::span<int> s;
    std::cout << sizeof(s) << std::endl;
}
16

Copy link
Owner Author

Choose a reason for hiding this comment

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

sizeofでサイズをチェックする方法も参考になります。ありがとうございます。

Copy link

Choose a reason for hiding this comment

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

補足すると、 Linux で使用される System V AMD64 ABI では 16 バイトまでのデータはレジスター渡しになるようです。

https://azelpg.gitlab.io/azsky2/note/prog/asm64/22_systemv.html

構造体全体を、2つの 8 byte 値で扱うことが出来る場合、(使用可能なレジスタがあれば) レジスタに格納されます。
2つの 8 byte 値で扱えない場合は、構造体全体がスタックに格納されます。

https://learn.microsoft.com/ja-jp/cpp/build/x64-calling-convention?view=msvc-170&utm_source=chatgpt.com

16 バイトの引数は参照渡しされます。

パフォーマンスが重要な場合については、コンパイラーから出力されるアセンブラを確認したり、マイクロベンチマークを取るのが良いと思います。そうでない場合は、ひとまず値渡しで良いと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

なるほど、ありがとうございます!参考になります。

int prev_max = 0;
int current_max = 0;
for (const int num : nums) {
int next_max = std::max(current_max, prev_max + num);
prev_max = current_max;
current_max = next_max;
}
return current_max;
}
};

Choose a reason for hiding this comment

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

std::span は初めて知りました。直接イテレータを渡しても書けそうな気がしたのですが、std::span を介することで vector のように扱えて直感的ということでしょうか?

Copy link
Owner Author

Choose a reason for hiding this comment

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

おっしゃる通りイテレーターを二つ渡しても書けると思います。利点もその通りだと思います。
イテレーターで渡した場合は

for (const int num : nums) {
...
}

for (it = begin; it < end; ++it) {
    int num = *it
    ...
}

のような形になりますね。
恐らく一つ前の問題(198. House Robber)で入力がvectorとして与えられていたので、その問題で書いたコードを再利用したいという思惑が無意識的に働きstd::spanを選択したんだと思います笑

Choose a reason for hiding this comment

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

わかりました。ありがとうございます!

```

## 他の人のコードを見てみる

https://github.com/tokuhirat/LeetCode/pull/36/files
https://github.com/irohafternoon/LeetCode/pull/39/files
https://github.com/Ryotaro25/leetcode_first60/pull/38/files

## 最終コード

ヘルパー関数をラムダ式で書いてみました。
Solutionクラスのprivateに含めるのとどちらが良いでしょうか?

```cpp
class Solution {
public:
int rob(std::vector<int>& nums) {
if (nums.size() == 1) {
return nums[0];
}
auto rob_linearly = [&nums](int begin, int end) -> int {
Copy link

Choose a reason for hiding this comment

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

ラムダ式は、長くなると、ソースコードを読むにあたり、目線をラムダ関数の前後で大きく動かさなければならなくなる場合があります。このため、読み手を疲れさせる場合があります。個人的には private なクラス関数としたほうが良いと思います。

ただ、イベントハンドラーを大量に書かなければならない場合や、並列計算フレームワークで計算ロジックをネストさせたい場合、キャプチャーを使いたい場合など、場合に応じて使い分けるのが良いと思います。

int prev_max = 0;
int current_max = 0;
for (int i = begin; i < end; ++i) {
int next_max = std::max(current_max, prev_max + nums[i]);
prev_max = current_max;
current_max = next_max;
}
return current_max;
};
return std::max(rob_linearly(0, nums.size() - 1), rob_linearly(1, nums.size()));
}
};
```