diff --git a/35. Search Insert Position/35. Search Insert Position.md b/35. Search Insert Position/35. Search Insert Position.md new file mode 100644 index 0000000..1399b61 --- /dev/null +++ b/35. Search Insert Position/35. Search Insert Position.md @@ -0,0 +1,79 @@ +# 35. Search Insert Position +## STEP1 +- 何も見ずに解いてみる +- ライブラリを使う(これは求められていないはず) +```python +import bisect + + +class Solution: + def searchInsert(self, nums: List[int], target: int) -> int: + return bisect.bisect_left(nums, target) +``` + +- 二分探索を書く +- left 以下のインデックスは target 未満、right 以上のインデックスは target 以上を満たすように書く。left と right が隣り合ったら right を返して終了。 +```python +class Solution: + def searchInsert(self, nums: List[int], target: int) -> int: + left = -1 + right = len(nums) + while left < right - 1: + middle = (left + right) // 2 + if nums[middle] < target: + left = middle + else: + right = middle + return right +``` + +## STEP2 +### プルリクやドキュメントを参照 +- https://github.com/olsen-blue/Arai60/pull/41/files + - 再帰でも書ける。while 文の方がシンプルな気がした。 +- https://discord.com/channels/1084280443945353267/1196498607977799853/1269532028819476562 +に従いSTEP1のコードを説明してみる。 +1. 二分探索を、 [false, false, false, ..., false, true, true, ture, ..., true] と並んだ配列があったとき、 一番左の true の位置を求める問題と捉える。true がない場合は len(nums) を返す。 +2. 位置を求めるにあたり、答えが含まれる範囲を狭めていく問題と捉える。 +3. 範囲は半開区間で考える。(left, right] に求める index が入っているように設定する。 +4. 初期値について:left と right が隣り合った時 (left + 1 = right) に終了し right を返すので left の初期値は -1, right の初期値は len(nums) とする。 +5. ループ不変条件について:4より left < right - 1。left 以下の index については false が確定、right 以上の index については true が確定している。 +6. 範囲を狭めるためのロジックについて:ループ中は left < right - 1 のため、middle = (left + right) // 2 は、left < middle < right である。nums[middle] < target の場合、5より left を middle とする。nums[middle] >= target の場合は right をmiddle とする。left < middle < right なので必ず範囲は狭まる。 + +- STEP1 では left = -1 である点と return right である点、が微妙に感じる引っかかるポイント。 + +- 以下は違うパターン。求める index が [left, right] に存在するように設定。初期値は自然に left = 0, right = len(nums) になる。left = right となったら終了。left 未満の index は target 未満、right 以上の index は target 以上となるようにする。nums[middle] < target の時は、middle で target 未満なので left はその右(+1)にする。right の位置では target 以上を保証しないといけないのでright = middleでなければいけない。右に狭める時は少なくとも +1 されるので狭まる。左に狭まる時は切り捨てなので狭まる。 +- 初期値がわかりやすい。left 更新時に +1 が必要。ループで保証すべきことを理解していれば +1 を忘れないのでこちらの方が良いと思った。 +```python +class Solution: + def searchInsert(self, nums: List[int], target: int) -> int: + left = 0 + right = len(nums) + while left < right: + middle = (left + right) // 2 + if nums[middle] < target: + left = middle + 1 + else: + right = middle + return left +``` + +- false と true の境界の位置を求めるというのはあまりイメージが湧かなかった。すぐ上のコードを半開区間らしいがあまりそうは感じない。区間の取り方をどのようにしても、ループ終了時に一つの要素(区間の区切り)を指していて欲しいという気持ちがあります。 + +## STEP3 +### 3回ミスなく書く +```python +class Solution: + def searchInsert(self, nums: List[int], target: int) -> int: + left = 0 + right = len(nums) + while left < right: + middle = (left + right) // 2 + if nums[middle] < target: + left = middle + 1 + else: + right = middle + return left +``` + +1分,1分,1分で3回Accept