diff --git a/Python3/33. Search in Rotated Sorted Array.md b/Python3/33. Search in Rotated Sorted Array.md new file mode 100644 index 0000000..939a436 --- /dev/null +++ b/Python3/33. Search in Rotated Sorted Array.md @@ -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 +``` + +### 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) + 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) + + 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 +```