Skip to content
Open
Show file tree
Hide file tree
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
34 changes: 34 additions & 0 deletions 35/step1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# step1 何も見ずに解く
問題の制約上binary searchを使う。(Rubyでも普通にループでやっても間に合いそうだが)

target以上となる最小のインデックスを返せば良い。
numsにそれを満たす値がなければ配列のサイズを返す。

インデックスleft以上、right未満の範囲を調査する。初期値はそれぞれ0, 配列のサイズとする。
(left + right) / 2をmiddleとして、nums[middle]の値が
target以上であればleft以上、middle未満のインデックスの範囲に答えがあるかもしれないのでright = middleとして探索を続ける
target未満であればmiddle + 1以上、right未満のインデックスの範囲に答えがあるかもしれないのでleft = middle + 1として探索を続ける

rightは常に答えの候補になり得えて、探索の度に答えに近づくので探索が打ち切られた時のright(left)が答えになるはず。

@akmhmgc akmhmgc Sep 28, 2025

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

理解が浅かった。

更新された時に言えることは、

  • leftより左はtargetより小さい
  • rightを含む右はtarget以上

なので探索が終わったタイミングでright == leftが答えになる。


時間計算量はO(logN)
空間計算量はO(1)

```ruby
# @param {Integer[]} nums
# @param {Integer} target
# @return {Integer}
def search_insert(nums, target)
l = 0
r = nums.size
while l < r
m = (r + l) / 2
if nums[m] >= target
r = m
else
l = m + 1
end
end
r
end
```
178 changes: 178 additions & 0 deletions 35/step2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# step2 他の方の解答を見る
## 両端を含める場合
https://discord.com/channels/1084280443945353267/1192736784354918470/1199018938005213234

target以上となる最小のインデックスを返せば良い。
numsにそれを満たす値がなければ配列のサイズを返す。

インデックスleft以上、right以下の範囲を調査する。初期値はそれぞれ0, 配列のサイズ - 1とする。
(left + right) / 2をmiddleとして、nums[middle]の値が
target以上であればleft以上、middle - 1以下のインデックスの範囲に答えがあるかもしれないのでright = middle - 1として探索を続ける
target未満であればmiddle + 1以上、right未満のインデックスの範囲に答えがあるかもしれないのでleft = middle + 1として探索を続ける

right + 1は常に答えの候補になり得えて、探索の度に答えに近づくので探索が打ち切られた時のright + 1が答えになるはず。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

rightより右はtarget以上
leftより左はtargetより小さい
という条件になるので、探索が終了したタイミングでのright + 1が答えになる。


```ruby
# @param {Integer[]} nums
# @param {Integer} target
# @return {Integer}
def search_insert(nums, target)
l = 0
r = nums.size - 1
while l <= r
m = (r + l) / 2
if nums[m] >= target
r = m - 1
else
l = m + 1
end
end
r + 1
end
```

考え方を自然言語にしてからコードを書くのはそれほど難しくないけど、人のコードを読んだ時に
コードから考え方に戻す作業が難しい。

> というわけで、書き方を固定してもいいけれども、幅のある表現を読めるようにしてくださいね、ということです。
https://github.com/Fuminiton/LeetCode/pull/41#discussion_r2080995529

## オプショナルな質問
https://github.com/Ryotaro25/leetcode_first60/pull/46#discussion_r1869993674


```ruby
# @param {Integer[]} nums
# @param {Integer} target
# @return {Integer}
def search_insert(nums, target)
left = 0
right = nums.size
while left < right
middle = (right + left) / 2
if nums[middle] >= target
right = middle
else
left = middle + 1
end
end
right
end
```

後続の問題を解いていて、理解してなかったことに気づいたので以下を考える。
`nums.back()`の話はRubyだとわからなかったので一旦飛ばす。
ref: https://github.com/Ryotaro25/leetcode_first60/pull/46#discussion_r1869993674

### 「2で割る処理がありますがこれは切り捨てでも切り上げでも構わないのでしょうか。」
たとえば、nums = [0, 1, 2], target = 3の時にleft,rightは0, 3なのでmiddleは2となる。
nums[middle]は2となり、nums[m] < targetなのでleft = middle + 1 = 2 + 1 = 3となり、leftが更新されずに無限ループになる。

### nums[middle] >= target とありますが、これは > でもいいですか。」
たとえば、nums = [0, 1, 2], target = 1の時、left,rightは0, 3なのでmiddleは1となる。
nums[middle]は1となり、nums[middle] > targetを満たさないので、left = 2となって、最終的に2を返す。
つまり、target以上ではなく、targetより大きくてインデックスが最も小さくなるインデックスを返すことになる。

### 「right の初期値は nums.size - 1 でもいいですか。」
right == leftが答えになるので、`nums.size`が答えになるパターンで間違った答えを返す。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

同じことかもしれませんが、right 以下に求めたいインデックスがあるような設定で書いているため初期値は nums.size でないといけない、というように思いました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

コメントありがとうございます!
具体例を考えないと反例を思いつかないというのが、まだ理解が浅い証拠かなと思い改めて整理してみました。

2cbcfb8


## 思考の整理
上でもまだしっくり来なかったので、以下のように整理した。
### 求めたいもの
target以上となる最小のインデックス

### 範囲の絞り方
- left
1. 自分より左側はtargetより小さい
2. ~~自分より左側はtarget以下~~
- これが言えても、targetより右がtarget以上とは言えない(以上かもしれないし、targetより大きいかもしれない)ので使えない
- right
3. 自分を含む右側はtarget以上
4. 自分より右側はtarget以上

1と3の組み合わせの場合、left == rightになるまで絞り込めば、leftより左はtargetより小さく、rightを含む右はtarget以上であると言えるのでleft = rightがtarget以上となる最小のインデックスと言える。

1と4の組み合わせの場合、left == right + 1になるまで絞り込めば、leftより左はtargetより小さく、rightより右側はtarget以上なのでleft == right + 1がtarget以上となる最小のインデックスと言える。

### middleの処理
- middle = (left + right) / 2 とする(切り捨て)
- middle = (left + right + 1) / 2 とする(切り上げ)

切り捨てではmiddleがleftと一致することがあるし、切り上げではmiddleがrightと一致することがある。

範囲の絞り方の1と3の組み合わせの場合は以下のように考える。
middleの値がtargetより小さいときは、leftをmiddle + 1に更新すれば、leftより左はtargetより小さいと言える。
middleの値がtarget以上であれば、rightをmiddleに更新すればrightを含む右はtarget以上だと言える。
rightをmiddleに更新するので、切り上げのパターンではright更新されないことがあり、無限ループになる。
なので切り上げの考えは使えない。

```ruby
def search_insert(nums, target)
left = 0 # leftより左側はtargetより小さい
right = nums.size # rightを含む右側はtarget以上
while left < right # 停止条件はleft == right
middle = (right + left) / 2 # 切り捨て
if nums[middle] >= target
right = middle # rightを含む右はtarget以上だと言える
else
left = middle + 1 # leftより左はtargetより小さいと言える
end
end
left
end
```

これは動かない
```ruby
def search_insert(nums, target)
left = 0 # leftより左側はtargetより小さい
right = nums.size # rightを含む右側はtarget以上
while left < right # 停止条件はleft == right
middle = (right + left + 1) / 2 # 切り上げ
if nums[middle] >= target
# middle == rightになり、更新されなくて無限ループになり得る
right = middle # rightを含む右はtarget以上だと言える
else
left = middle + 1 # leftより左はtargetより小さいと言える
end
end
left
end
```

範囲の絞り方の1と4の組み合わせの場合は以下のように考える。
middleの値がtargetより小さいときは、leftをmiddle + 1に更新すれば、leftより左はtargetより小さいと言える。
middleの値がtarget以上であれば、rightをmiddle - 1に更新すればrightより右はtarget以上だと言える。
この場合は、切り上げでも切り捨てでもmiddleがleft,right両方ともに一致することがないので無限ループせずに動く。

```ruby
def search_insert(nums, target)
left = 0 # leftより左側はtargetより小さい
right = nums.size - 1 # rightより右側はtarget以上
while left <= right # 停止条件はleft == right + 1
middle = (right + left) / 2 # 切り捨て
if nums[middle] >= target
right = middle - 1 # rightより右はtarget以上だと言える
else
left = middle + 1 # leftより左はtargetより小さいと言える
end
end
left
end
```

```ruby
def search_insert(nums, target)
left = 0 # leftより左側はtargetより小さい
right = nums.size - 1 # rightより右側はtarget以上
while left <= right # 停止条件はleft == right + 1
middle = (right + left + 1) / 2 # 切り上げ
if nums[middle] >= target
right = middle - 1 # rightより右はtarget以上だと言える
else
left = middle + 1 # leftより左はtargetより小さいと言える
end
end
left
end
```
20 changes: 20 additions & 0 deletions 35/step3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# step3 3回続けて10分以内に書いてエラーを出さなければOKとする

```ruby
# @param {Integer[]} nums
# @param {Integer} target
# @return {Integer}
def search_insert(nums, target)
left = 0
right = nums.size
while left < right
middle = (right + left) / 2
if nums[middle] >= target
right = middle
else
left = middle + 1
end
end
right
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

一文字変数を使っているstep1のコードと比較して読みやすくなっていると感じました。

```
1 change: 1 addition & 0 deletions 35/step4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## step4 レビューを受けて解答を修正