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 Python3/33. Search in Rotated Sorted Array.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
## Step 1. Initial Solution

- 始めは最小値が分かればそこからは二分探索するだけだと考えて最小値を二分探索→再度二分探索、という方針を考えた
- ただ、最小値が分かったところで結局分岐は沢山発生するのであまり意味がないことに気が付いた
- なるべくシンプルに考えたかったが、上手く条件をまとめられなかった
- left, middle, rightとtargetの大小関係を考えると l < m < r, m < r < l, r < l < mの3パターンにtargetがどこに入るかで×4通りを考えることになる
- よりシンプルに2つの値とtargetの位置関係を考えた
- これを関数において、left, middleとtargetの関係+middle, rightとtargetの関係が分かれば十分
- nums[left] < nums[middle] < nums[right], nums[middle] < nums[right] < nums[left], nums[right] < nums[left] < nums[middle]の3パターンについて脳内でアルゴリズムを頑張って動かして合っていることを確認
- 閉区間でやるとwhile文の中で長さ1のリストを処理しないので半開区間に修正して以下

```python
class Solution:
def search(self, nums: List[int], target: int) -> int:
def is_between(left: int, right: int, target: int) -> int:
if nums[left] <= target <= nums[right-1]:
return True
if target <= nums[right-1] < nums[left]:
return True
if nums[right-1] < nums[left] <= target:
return True
return False

left = 0
right = len(nums)
while left < right:
middle = (left + right) // 2
if is_between(left, middle, target):
right = middle
continue
if is_between(middle, right, target):
left = middle
continue
return -1
return left

Choose a reason for hiding this comment

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

私の理解不足かもしれませんが、left = 0, middle = 0 となった時に is_between の right-1 は -1 になり、結果的に正しく動いているように見えて不思議に思いました。

```

### Complexity Analysis

- 時間計算量:O(log n)
- 空間計算量:O(1)

## Step 2. Alternatives

- 分岐が多くなってしまっても何も書かないよりは何か書けた方が良い
- コードとしては処理が多くて読みにくいが複雑な問題ではあるので初見でも理解はできる
- https://github.com/tokuhirat/LeetCode/pull/43/files#diff-681ab9d5fa98926ad078bf1aed23e33360ee5630c5c021079ce3f4fde79eb2d9R1
- これとかを読んでも実際に手作業するときにやりそうな方法だと感じた
- https://github.com/olsen-blue/Arai60/pull/43/files#diff-0691fd54173ad5183bf1c632f87cc4a7353908565057fa6b9facae8e8ec5ec4bR16
- 自分は大した技量も知識もないのに問題を綺麗に解こうとしすぎているのかもしれない
- 二分探索分かっているようで分かっていない感覚が強いがその正体がこの辺りにありそうだと感じた
- どのような書き方をするのかの選択肢が多いので一度止まって考えることが多いのも難しさの理由だと感じる
- 理解しやすい問題に置き換えて考えることの重要さが分かる
- https://github.com/Yoshiki-Iwasa/Arai60/pull/35#discussion_r1699552857
- bisect_leftのkeyを上手く設定してやる方法があるらしい
- https://github.com/olsen-blue/Arai60/pull/43/files#diff-0691fd54173ad5183bf1c632f87cc4a7353908565057fa6b9facae8e8ec5ec4bR142
- そもそも整序されていないリストをbisect_leftでやろうとするのが選択肢になかった
- 二つ条件を組み合わせれば確かに右肩上がりでtargetの点だけ違う値が出る関数を作ること自体は難しくはない
- 普通の整序リストなら二つ目の条件だけでfalse, false, false, true, true, …の最初のtrueを探せば良い
- 今回は同じ方法でやるとtrue, true, true, …false, false, true, true, …のようなパターンがあるのでそれに対応して手前のtrueをfalseにしてやらないといけない
- 頑張ってコードを理解するより使いたくなる気持ちを理解しておきたい
- 一つ目の条件の優先度を高くする必要はないことに気が付いた
- targetを越えないとスコアは2以上にならない

```python
class Solution:
def search(self, nums: List[int], target: int) -> int:
def score_position(num: int) -> int:
return (num <= nums[-1]) + (num >= target)
Copy link

Choose a reason for hiding this comment

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

(num <= nums[-1], num) で本当はいいみたいですね。

index = bisect_left(nums, score_position(target), key=score_position)
if nums[index] == target:
return index
return -1
```


## Step 3. Final Solution

- こういう探索の仕方があるということを理解できたのは収穫だった

```python
class Solution:
def search(self, nums: List[int], target: int) -> int:
def score_num(num: int) -> int:
return -(num > nums[-1]) + (num >= target)
Copy link

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.

ドキュメントにも書かれてました。初めて知りました。
https://docs.python.org/3/library/stdtypes.html#typebool

In many numeric contexts, False and True behave like the integers 0 and 1, respectively. However, relying on this is discouraged; explicitly convert using int() instead.


def binary_search_from_left(nums: list[int], target: int, key: Callable[[int], int]) -> int:
begin = 0
end = len(nums) - 1
while begin < end:
middle = (begin + end) // 2
if key(nums[middle]) < key(target):
begin = middle + 1
else:
end = middle
return begin

index = binary_search_from_left(nums, target, key=score_num)
if nums[index] == target:
return index
return -1
Copy link

Choose a reason for hiding this comment

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

面白い書き方だと思いました。
num > nums[-1]num >= nums[0]としても動きますかね?
個人的にはscore_numは次のように書いてしまえば初見でもわかりやすくなるかもしれないと思いました。(今の書き方もエレガントで面白いですが)

if target > nums[-1]:
    if num >= target or num <= nums[-1]:
        return 1
if target <= nums[-1]:
    if num >= target and num <= nums[-1]:
        return 1
return 0

Copy link

Choose a reason for hiding this comment

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

もしこうするならscore_numはis_past_targetみたいな名前のbooleanを返す関数にしてしまって、
if key(nums[middle]) < key(target)みたいなところはif not is_past_target(num)みたいな形にしてもいいかもしれません。

```