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
255 changes: 255 additions & 0 deletions medium/33/answer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
# Step1

かかった時間:解けず

計算量: N=len(nums)として

時間計算量:O(logN)

空間計算量:O(1)

2分探索
```python
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)
while left < right:
middle = (left + right) // 2
if nums[middle] == target:
return middle

if nums[0] <= nums[middle]:
if nums[0] <= target < nums[middle]:
right = middle - 1
else:
left = middle + 1
else:
if nums[middle] < target <= nums[-1]:
left = middle + 1
else:
right = middle - 1

return -1
```
思考ログ:
- targetの位置、middleの位置で場合わけをしていたら、よく分からなくなってしまった
- 一回で探索する方法が思いつかなくても、前回の153.の結果を使って最小値を求めたあと、もう一度探索する方法は思いつくはず

Choose a reason for hiding this comment

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

自分も初め一回で解こうとして場合分けがよくわからないことになりました。

- 綺麗に解こうとせず、愚直でもやってみる精神が足りていない気がする
- 探すものはtarget以上になる最小の数字の場所
- pivotの左か右の領域が必ず(狭義)単調増加列になっていることを利用する
- 単調増加列の中にtargetがある可能性を確認し、結果に応じて探索範囲を狭める(right = middle - 1 or left = middle + 1)
- 道中でtargetが見つかればそのindexを返し、そうでなければ見つからなかったことになるので-1を返す
- この実装だとrightより右側の領域が、target以上になる最小の数字 or それより右の数字、とはならない
- 例えば```nums = [2,0,1]```, ```target = 3```
- これは0で止まるが、0, 1は、numsの中で3以上になる最小の数字 or それより右の数字ではない
- この場合は[F, F, F]なのでleft=0で止まって欲しい
- これを満たすようにうまく条件を設定できるのか、少し考えたがよく分からなかった。。

再帰
```python
class Solution:
def search(self, nums: List[int], target: int) -> int:
def search_helper(left: int, right: int) -> int:
middle = (left + right) // 2
if nums[middle] == target:
return middle

if left == right:
return -1

if target <= nums[-1]:
if target <= nums[middle] <= nums[-1]:
return search_helper(left, middle)
else:
return search_helper(middle + 1, right)
else:
if (
target <= nums[middle]
or nums[middle] <= nums[-1]
):
return search_helper(left, middle)
else:
return search_helper(middle + 1, right)

return search_helper(0, len(nums))
```
- targetとnumsの端を比較してもいい
- 少し冗長になるが、全部nums[-1]と比較するようにしてもいい

O(N)ループ
```python
class Solution:
def search(self, nums: List[int], target: int) -> int:
assert len(nums) > 0

for i in range(len(nums)):
if nums[i] == target:
return i

return -1
```

# Step2

講師役目線でのセルフツッコミポイント:
- 条件の処理が込み入っているので、適当に関数にした方が良さそう

参考にした過去ログなど:
- https://github.com/olsen-blue/Arai60/pull/43
- cmp関数を初めて知った、(a < b, a == b, a > b) -> (-1, 0, 1)に割り当てる
- python3.0以前は組み込み関数としてあったらしい
- https://docs.python.org/ja/3.7/whatsnew/3.0.html
- 説明が分かりやすいのでメモしておく
> A : 崖の前/後が F/T つまり 0/1 で表されている。この時点で、この後Bでいくら頑張っても挽回できない絶対的な格付け序列が作られてしまうイメージ。
> - 今回は、後ろの方が高い格のイメージ。(年末年始の某番組を思い出しました。)
> - (改良前のコードの、-2で差をつける処理は、これに近いことをしていたんですね。)

> B : Aで決められた枠の中で、さらに細かい格付け序列を作るために、cmp(x, target)を利用している。
> - targetがnums内にあれば、...-1, -1, 0(targetの位置), 1, 1, ... となる。bisect_leftの返り値は、target の位置インデックスになる。
> - targetがnums内になければ、...-1, -1, 1*, 1, ... となる。bisect_leftの返り値は、1* の位置インデックスになる。
- https://github.com/saagchicken/coding_practice/pull/14
- https://github.com/saagchicken/coding_practice/pull/9
- https://github.com/hroc135/leetcode/pull/41
> 1. true: nums中のtargetと一致する要素とそれより右側の要素、
- これを考えなかった、targetが入っていなかったら全部Falseになると考えればよかった
- https://github.com/Ryotaro25/leetcode_first60/pull/47
> 問題のモデル化
> 探索空間の定義
> 初期値の設定
> ループ不変条件の設定
> 探索ロジックの設計
> 検証
> 実行と反省
> といった流れで考えられるようにしたほうが良いと思います。
- https://github.com/Mike0121/LeetCode/pull/45
- https://github.com/Yoshiki-Iwasa/Arai60/pull/36
- https://github.com/Yoshiki-Iwasa/Arai60/pull/36/files#r1704471895
- https://github.com/fhiyo/leetcode/pull/44
- offsetをとって昇順に戻す方法
- https://github.com/sakupan102/arai60-practice/pull/44
- https://github.com/sakzk/leetcode/pull/7/files
- https://github.com/SuperHotDogCat/coding-interview/pull/10
- https://github.com/shining-ai/leetcode/pull/43
- https://github.com/hayashi-ay/leetcode/pull/49

bisect_left*2
```python
class Solution:
def search(self, nums: List[int], target: int) -> int:
def is_between_min_val_and_last_val(num: int) -> bool:
return num <= nums[-1]
Comment on lines +140 to +141

Choose a reason for hiding this comment

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

この一行の処理を関数化するメリットがわからずでした。

Choose a reason for hiding this comment

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

あ、bisect_leftのkeyで使うためなのですね。なるほど。


min_val_index = bisect_left(
nums,
True,
key=is_between_min_val_and_last_val
Copy link

Choose a reason for hiding this comment

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

lambda を使ってもいいかもしれません。

Copy link
Owner Author

Choose a reason for hiding this comment

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

そうですね。無理やり名前をつけるくらいならこの程度の処理ならlambdaで処理したほうが綺麗でわかりやすいかもと思いました。

)

if target <= nums[-1]:
lo = min_val_index
hi = len(nums)
else:
lo = 0
hi = min_val_index

first_ge_target_index = bisect_left(
nums,
target,
lo=lo,

Choose a reason for hiding this comment

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

ご存じかとは思いますが、
def insort_left(a, x, lo=0, hi=None, *, key=None):
と書かれたときloとhiは位置引数でも大丈夫なので、
bisect_left(nums, target, lo, hi)
で済みますね

Copy link
Owner Author

Choose a reason for hiding this comment

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

コメントありがとうございます。
気持ちとしては、読者がbisect_leftのシグネチャを熟知していなくても安心して読めるので、書くようにしています。

こんなコメントもありました。
Mike0121/LeetCode#45 (comment)

余談ですが、昔numpyのclipというメソッドをnp.clip([1,2,3], 2, 3)のつもりでnp.clip(2, 3, [1,2,3])と書いて酷い目にあった思い出があります。

hi=hi
)

if nums[first_ge_target_index] == target:
return target_index

return -1
```
思考ログ:
- bisect_*で、keyやlo, hiを使ったことがなかったのでドキュメントを振り返りながら実装した
- https://docs.python.org/ja/3.13/library/bisect.html
- https://github.com/python/cpython/blob/main/Lib/bisect.py

bisect_left*1
```python
class Solution:
def search(self, nums: List[int], target: int) -> int:
def is_le_last_val_and_ge_target(num):
return (num <= nums[-1], target <= num)

first_ge_target_index = bisect_left(

Choose a reason for hiding this comment

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

少し変数名が分かりにくく感じました。
lower_bound_index等はいかがでしょうか🙇‍♂️

Choose a reason for hiding this comment

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

geの意味が、捉えられずでした。

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://ja.wikipedia.org/wiki/%E9%96%A2%E4%BF%82%E6%BC%94%E7%AE%97%E5%AD%90

Copy link

Choose a reason for hiding this comment

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

Tex でもたしか \leq, \geq があったかと思います。

nums,
is_le_last_val_and_ge_target(target),
key=is_le_last_val_and_ge_target
)

if nums[first_ge_target_index] == target:
return first_ge_target_index

return -1
```
思考ログ:
- これは自力では思いつかないなと思った
- [num]にtargetがあるか探す問題から[f(num)]にf(target)があるか探す問題に変換する
- 具体例で考えてみる
- ```nums = [4,5,6,7,0,1,2]```の場合
- ```target = 5```とすると、```key(target) = (False, True)```
- 2(=nums[-1])以下の要素は、全て```(True, False)```になるので、True判定
- 2(=nums[-1])より大きい要素は、target以上だったときにTrue判定が返る
- ```target = 1```とすると、```key(target) = (True, True)```
- 2(=nums[-1])以下の要素で、target以上だったとき```(True, True)```にTrue判定が返る

# Step3

かかった時間:5min

```python
class Solution:
def search(self, nums: List[int], target: int) -> int:
def find_min_index(nums: list[int]) -> int:
left = 0
right = len(nums)
while left < right:
middle = (left + right) // 2
if nums[middle] <= nums[-1]:
right = middle
else:
left = middle + 1

return left

min_index = find_min_index(nums)
Copy link

Choose a reason for hiding this comment

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

bisect_left の範囲を一回変数に置く手もあります。

if target <= nums[-1]:
candidate_index = bisect.bisect_left(
nums,
target,
lo=min_index,
hi=len(nums)
)
else:
candidate_index = bisect.bisect_left(
nums,
target,
lo=0,
hi=min_index
Comment on lines +224 to +234
Copy link

@olsen-blue olsen-blue Mar 29, 2025

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.

pep8のコレに引っかかるかなと思ったんですけど、数えたら70文字でした。
ただそれでもまあまあ長いので、折り返してもいいかなと感じています。

)

if (
candidate_index < len(nums)
and nums[candidate_index] == target
):
return candidate_index

return -1

```
思考ログ:
- 無理せず2回探索するのが自然な気がしたのでこれに落ち着いた
- 最後の```candidate_index < len(nums) ```は必要ないのだけど、その確認を読み手にさせないためにも置いておくといいと思った
Copy link

Choose a reason for hiding this comment

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

target <= nums[-1] のチェックがあるので candidate index が len(nums) にならないということですよね?
私としては candidate index < len(nums) のチェックがあるとむしろそうでない場合があるのかと思ってしまうので、チェックの代わりにコメントを残すか assert を入れるかなと思いました。

- https://github.com/hayashi-ay/leetcode/pull/49/files#r1527168476

# Step4

```python
```
思考ログ: