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
132 changes: 132 additions & 0 deletions Python3/35. Search Insert Position.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
## Step 1. Initial Solution

- bisect.bisect_leftで答えは得られる
- 自分でこれを実装する
- begin, endの定義は他にもやりようがある
- ここでは[begin, end)で定義している
- begin-1より大きく、endより小さい位置にある
- begin == endになったらその位置に入れてあげれば良い

```python
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
def binary_search(begin: int, end: int) -> int:
Copy link

Choose a reason for hiding this comment

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

関数名には、どのような値が返ってくるかを表す名前を付けたほうが、ソースコードが理解しやすくなると思います。

middle = (begin + end) // 2
if begin == end or target == nums[middle]:
return middle
if target < nums[middle]:
return binary_search(begin, middle)
if target > nums[middle]:
return binary_search(middle + 1, end)
return binary_search(0, len(nums))
```

### Complexity Analysis

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

## Step 2. Alternatives

- 他にも書き方を試してみる
- target == nums[middle]をなくす

```python
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
def binary_search(begin: int, end: int) -> int:
middle = (begin + end) // 2
if begin == end:
return middle
if target <= nums[middle]:
return binary_search(begin, middle)
else:
return binary_search(middle + 1, end)
return binary_search(0, len(nums))
```

- iterationで解く

```python
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
begin = 0
end = len(nums)
while begin < end:
middle = (begin + end) // 2
if target <= nums[middle]:
end = middle
else:
begin = middle + 1
return begin
```

- 確かにleft, rightの方が良く見かけるのを思い出した
- https://github.com/tokuhirat/LeetCode/pull/41/files#diff-2b5524d310a8ba9fe7670e916be44468c8caec3d8aee64536cda8aa952db07f6R46

> pythonではオーバーフローを考慮しなくて良いが、考慮する必要がある言語だと mid = left + (right - left) // 2のようにする。
>
- https://github.com/hayashi-ay/leetcode/pull/40/files
- これも前にやった気がする
- Pythonのint → Cのlong, Pythonのfloat → Cのdouble

```python
import sys

i = sys.maxsize
print(i)
# 9223372036854775807
print(i == i + 1)
# False
i += 1
print(i)
# 9223372036854775808

f = sys.float_info.max
print(f)
# 1.7976931348623157e+308
print(f == f + 1)
# True
f += 1
print(f)
# 1.7976931348623157e+308
```

- 今回は一つ一つの数字はユニークだが、連続する場合に前に入れようと思うとtarget == nums[middle]は要注意
- https://github.com/olsen-blue/Arai60/pull/41/files#diff-912c29ce0f491ca88b1474ced97db89f2d33a36a34cd0f516f5b523e400af176R67

## Step 3. Final Solution

- 閉区間

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

@potrue potrue Aug 13, 2025

Choose a reason for hiding this comment

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

right = middleのほうが個人的には良いと思いました。(target <= nums[middle]であってもmiddleが答えになりえるので)
これだと最後left == right + 1の状況になってleftが返されて終わりという感じになると思いますが、少しわかりにくいと感じました(捉え方によるのかもしれません)

else:
left = middle + 1
return left
```

- 開区間

Choose a reason for hiding this comment

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

個人的には、開区間か閉区間の選択ではなく、leftとrightがそれぞれ何を満たして欲しいものとして考えるかから始めることが重要な気がしています。

以下の方式であれば、
「rightは必ずtarget以上で、leftは必ずtarget未満である」というのが前提なので、停止するのはleftとrightが隣り合う時であり、targetが全要素より大きい/小さいを満たす初期値は〜、更新の仕方は〜と考えていくイメージです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

その前提を決めることが開区間か閉区間かを決めることに相当するような気がしているのですが違うのでしょうか?

ちなみにこのやり方は、以前SWE協会の勉強会における模擬面接で「この二分探索を開区間で書けますか?」というような質疑をしていたのを思い出してのことです。

Copy link

@nodchip nodchip Jan 25, 2026

Choose a reason for hiding this comment

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

背景として、「開区間」「閉区間」という単語の意味を分からないまま、二分探索のアルゴリズムの理解に使用しようとしている方がいらっしゃいました。単語の意味を理解しないまま理解を進めようとしたため、二分探索自体も理解できていませんでした。そのため、 SWE 協会の過去のレビューに置いては、開区間・閉区間という単語を使わず、以下を明示的に理解することから始めるという流れができたように思います。

  • left より左側にある要素が満たす条件は何か?
  • left の位置にある要素が満たす条件は何か?
  • left より右側にある要素が満たす条件は何か?
  • right より左側にある要素が満たす条件は何か?
  • right の位置にあるにある要素が満たす条件は何か?
  • right より右側にある要素が満たす条件は何か?
  • 二分探索の開始時の left の位置はどこか?
  • 二分探索の開始時の right の位置はどこか?
  • ループの不変条件は何か?
  • ループが終了する条件は何か?
  • middle はどのように計算するか?
  • middle の位置の要素がどのような条件の時に、 left を変更するか?
  • left を変更する場合、何に変更するか?また、なぜその変更は妥当なのか?
  • middle の位置の要素がどのような条件の時に、 right を変更するか?
  • right を変更する場合、何に変更するか?また、なぜその変更は妥当なのか?
  • middle の位置の要素が目的の要素だった場合、 early return するか?
  • 無限ループにならないか?
  • 最終的に返す要素、または位置は何か?

ご自身が書かれたコードについて、上記を一通り答えられるか確認することをお勧めいたします。また、他の参加者の過去の回答を読み、上記について一通り答えられるかどうか確認すると、理解が深まると思います。

二分探索は、自分なりの書き方で書くことができ、かつ、他の方が書かれた書き方を読んで理解できることが望ましいと思います。


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

- 反対側の半開区間(-1, len(nums)-1]だけよく分からなかった