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

```cpp
class Solution {
public:
int findMin(vector<int>& nums) {
int left = 0;
int right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[right]) left = mid + 1;
if (nums[mid] < nums[right]) right = mid;
}
return nums[left];
}
};
```

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

https://github.com/tokuhirat/LeetCode/pull/42/files
https://github.com/ryosuketc/leetcode_arai60/pull/31/files
https://github.com/takuya576/leetcode/pull/2/files
https://github.com/Ryotaro25/leetcode_first60/pull/46/files
https://github.com/goto-untrapped/Arai60/pull/24/files

numsの最初や最後の要素をnums[mid]との比較対象に選んでいるコードもあったが、個人的には[left, right]に入っていない要素はループ中考えなくてよい感じにしたいと思いました。再帰関数みたいなイメージです。
(そういえばこれ書いているときは思いつきませんでしたが再帰関数でも書けますね)

ライブラリ関数を使って書けないかなと考えていたら、[std::partition_point](https://cpprefjp.github.io/reference/algorithm/partition_point.html) というのを見つけました。
これを使うと「nums[0]未満の最初の要素を見つける、なかったらnums[0]を返す」というアプローチで書けそう。

```cpp
class Solution {
public:
int findMin(vector<int>& nums) {
auto it = std::partition_point(nums.begin(), nums.end(), [&](int x) {
return x >= nums[0];
});
if (it != nums.end()) {
return *it;
} else {
return nums[0];
}
}
};
```

値がユニークでなかったとき(nums[mid]とnums[right]が同じだったとき)はどうするべきだろう?
[std::invalid_argument](https://learn.microsoft.com/ja-jp/cpp/standard-library/invalid-argument-class?view=msvc-170) をthrowしてみる?
geminiに相談してみたらrightを1個下げることで正しい答えが求まるようになると言われました。なるほど。

ロバストにするならこんな感じでしょうか?

```cpp
class Solution {
public:
int findMin(std::vector<int>& nums) {
if (nums.empty()) throw std::invalid_argument("nums are empty");
int left = 0;
int right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[right]) {
left = mid + 1;
} else if (nums[mid] < nums[right]) {
right = mid;
} else { // if (nums[mid] == nums[right])
--right;
Copy link

Choose a reason for hiding this comment

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

すべての値が等しい場合、 O(n) になるように思いました。

}
}
return nums[left];
}
};
```

ただこれでも(uniqueでないとしても)ソートされてることは仮定しないといけないので、実際にはemptyかどうかのチェックぐらいでいいかな?
emptyのとき、indexを返すだけだったら-1でも返しておけばいいかなと思ったが、中身を返すとなるとエラーをthrowするぐらいしかないかもしれない。intの上限を返すのもちょっと変な気がする。


## 最終コード

条件分岐って全部単体のifで回すよりelseとかcontinueとかつけておいた方が余計な条件分岐の判定が少なくなってちょっと早くなるとかあるんでしょうか?
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.

なるほど。アセンブラーを出力するのは考えませんでした。
https://godbolt.org/
このサイトでやってみたところ、x86-64 clang 20.1.0というのでやった場合は-O2, -O3といった最適化オプションを付けた場合は変わらないようです 👀

コンパイラが勝手に最適化してくれたりすることもあるんですかね。
もしかしたらこのelseは少し見づらいかも・・・

Choose a reason for hiding this comment

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

全体としては良いと思いました。if 文には {} ついている方が個人的には好みです。


```cpp
class Solution {
public:
int findMin(std::vector<int>& nums) {
if (nums.empty()) throw std::invalid_argument("nums is empty");
int left = 0;
int right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[right]) left = mid + 1;
else right = mid;
}
return nums[left];
}
};
```