Skip to content

704. Binary Search#13

Open
yamashita-ki wants to merge 1 commit intomainfrom
leetcode/704
Open

704. Binary Search#13
yamashita-ki wants to merge 1 commit intomainfrom
leetcode/704

Conversation

@yamashita-ki
Copy link
Owner

int left = 0, right = nums.length - 1;

while (left <= right) {
int middle = left + ((right - left) / 2);
Copy link

Choose a reason for hiding this comment

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

int middle = left + (right - left) / 2;

で良いと思います。

- 計算量
- 時間:O(logN)
- 空間:O(1)
- 一番よく見る二分探索
Copy link

Choose a reason for hiding this comment

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

一番良く見るかどうか微妙に感じました。

left = middle + 1;
}
}
return (left > 0 && nums[left - 1] == target) ? left - 1 : -1;
Copy link

Choose a reason for hiding this comment

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

この部分、二分探索の問題の設定があまり上手くなく、最後に無理やりつじつまを合わせているように感じました。もしかすると、二分探索の理解が浅いせいもあるかもしれません。

理解の確認のため、以下の質問に答えてみていただけますか?

  1. 区間には何が含まれますか?
  2. left が指す対象は何ですか?
  3. right が指す対象は何ですか?
  4. ループの不変条件 (left < right) を決めるとき、どのように決めましたか?
  5. int mid = (left + right) / 2; のほうがシンプルですが、なぜ int middle = left + ((right - left) / 2); なのですか?
  6. if 文の条件式は、なぜ nums[middle] > target なのですか?
  7. left = middle + 1;+ 1 の部分は、なぜ + 1 なのですか? - 10 でないのはなぜですか?
  8. right = middle; にはなぜ - 1+ 1 を付けていないのですか?
  9. ループを抜けた段階で left は何を指していますか?
  10. 最後の行を return nums[left] == target にできるように、二分探索のコードを書き替えることはできますか?

Copy link
Owner Author

@yamashita-ki yamashita-ki Oct 1, 2025

Choose a reason for hiding this comment

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

私の理解を深めるために質問を用意してくださりありがとうございます!
野田さんの求めている答えになっているか不安ですが、回答させてください。
まず、背景として他人のコードを読んだ時に C++のupper_boundのような書き方をしている回答がありその書き方に興味をもったことからこの書き方をしていました。

区間には何が含まれますか?

[left, right)、leftからright-1までの配列が区間には含まれると思っています。

left が指す対象は何ですか?

targetが存在する可能性がある最小のインデックスという認識です。

right が指す対象は何ですか?

こちらはtargetが存在する可能性のある最大のインデックスの次のインデックスです。

ループの不変条件 (left < right) を決めるとき、どのように決めましたか?

こちらは [left, right) という区間に探索すべき要素がまだ残っているか見つけるためです。

int mid = (left + right) / 2; のほうがシンプルですが、なぜ int middle = left + ((right - left) / 2); なのですか?

こちらはオーバーフローを防ぐためです。
与えられた配列がかなり長い場合 (left + right) / 2 だと加算する段階で Integer.MAX_VALUE を超えてオーバーフローする可能性があるのかなと思っています。

if 文の条件式は、なぜ nums[middle] > target なのですか?

背景として書いた通り、今回書いて見たかったのは単に配列の中にtargetがあるかどうか探索するだけでなくより多くの情報(具体的にはtargetより大きい値が出てくる最小のインデックス)をしれるコードをだったからです。
今回の問題には求められていない情報ですが。

left = middle + 1; の + 1 の部分は、なぜ + 1 なのですか? - 1 や 0 でないのはなぜですか?

left = middle + 1; が実行される時点で nums[middle] はtargetと等しいかそれより小さいことがわかっているため探索区間から除外するためです。
繰り返しになりますが、今回のコードの目的として「targetより大きい値が出てくる最小のインデックスを知りたい」という思いで書いています。

right = middle; にはなぜ - 1 や + 1 を付けていないのですか?

right = middle が実行されるということは nums[middle] がtargetより大きいことがわかっているので、middleをtargetとすることで [left, right) という条件が保てるためです。
もし right = middle - 1 としてtargetが nums[middle - 1] だった場合探索区間から外れてしまいます。

ループを抜けた段階で left は何を指していますか?

ループを抜けた段階でleftはtargetより大きい値が出てくる最小のインデックスを指します。

最後の行を return nums[left] == target にできるように、二分探索のコードを書き替えることはできますか?

書き換えると私がStep3に書いたコードになるのかなと思っています。

Copy link

Choose a reason for hiding this comment

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

自分の解釈と違っていました。自分が読んだときの解釈は以下の通りです。

仮に配列の [left, right) の範囲の要素が区間に含まれていると仮定すると、 while (left < right) { と書いてあることから、ループを抜けたタイミングで区間には要素が含まれなくなります。これは違和感を感じます。仮にループを抜けた段階で、 target より大きい最小の値が区間内に存在するようにするというのであれば、 while (left + 1 < right) { と、区間の中に要素が一つだけ残るようにすると思います。

仮に left と right が要素と要素の間の境界を指し示しており、target 以下と target 超の要素の境目を探そうとしているのだと仮定します。すると、 [left, right] という区間には、探そうとしている境界が含まれ、 left == right となったときに、区間内には探そうとしている境界のみが残り、 配列の (境界のインデックス - 1) の位置に target が存在する可能性がある、と解釈できます。

区間には何が含まれますか?

[left, right)、leftからright-1までの配列が区間には含まれると思っています。

言葉の使い方に違和感を感じました。区間には、配列の left から right - 1 までの要素が含まれる、でしょうか。

ただし、ループを抜けた段階で区間には要素が含まれなくなっているため、二分探索の考え方とコードが矛盾しているように感じました。

left が指す対象は何ですか?

targetが存在する可能性がある最小のインデックスという認識です。

target より大きい値が存在する可能性がある最小のインデックス、でしょうか。

[left, right) という条件

こちらも言葉の使い方に違和感を感じました。「[left, right) という条件」とはどのような意味でしょうか? [left, right) は区間を表しており、条件を表してはいないように思います。

Copy link
Owner Author

@yamashita-ki yamashita-ki Oct 1, 2025

Choose a reason for hiding this comment

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

ご確認ありがとうございます。

区間には、配列の left から right - 1 までの要素が含まれる、でしょうか。

はい、ご認識の通りです。

ループを抜けた段階で区間には要素が含まれなくなっているため、二分探索の考え方とコードが矛盾しているように感じました。

確かに処理の途中までは「区間には、配列の left から right - 1 までの要素が含まれる」が正しくループ終了時にはleftとrightが同じ値になり境界が確定するという説明がただしかったですね。

target より大きい値が存在する可能性がある最小のインデックス、でしょうか。

こちらもご認識の通りです。

[left, right) という条件

こちらも言葉の使い方に違和感を感じました。「[left, right) という条件」とはどのような意味でしょうか?

[left, right) は区間を表す表現で条件ではなかったですね。
改めて回答させてください。

right = middle; にはなぜ - 1 や + 1 を付けていないのですか?

right = middle が実行されるということは nums[middle] がtargetより大きいことがわかっているので、middleをtargetとすることで [left, right) という条件が保てるためです。

こちらも変なこと(ex: middleをtargetとする)書いてますね 🙇
ここで言いたかったこととしては、「right = middle とすることで、[left, right) の範囲内に境界が存在することを保てるから」が正しかったです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

@nodchip
参考までにどの回答が正しく、どの回答が誤りだったか教えていただけるとありがたいです。
お手数おかけしますがよろしくお願いしますmm

Copy link

Choose a reason for hiding this comment

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

ループを抜けた段階で区間には要素が含まれなくなっているため、二分探索の考え方とコードが矛盾しているように感じました。

確かに処理の途中までは「区間には、配列の left から right - 1 までの要素が含まれる」が正しくループ終了時にはleftとrightが同じ値になり境界が確定するという説明がただしかったですね。

この説明を聞いた感じですと、二分探索の問題の設定があやふやなように感じました。今回の問題において、二分探索の問題の設定を、 target より大きい最小の要素の位置を特定する問題と考えていますか?それとも target 以下の要素と target 超の要素の境界を特定する問題と考えていますか?
前者で考えているのであれば、最終的に出力されるものは、要素のインデックスであり、境界という概念は登場しないように思います。後者で考えているのであれば、最終的に出力されるものは、境界のインデックスであり、要素のインデックスは登場しないと思います。
前者で考えているのであれば、区間の中には要素が含まれることになると思います。後者で考えるのであれば、区間の中には境界が含まれることになると思います。

こちらも変なこと(ex: middleをtargetとする)書いてますね 🙇
ここで言いたかったこととしては、「right = middle とすることで、[left, right) の範囲内に境界が存在することを保てるから」が正しかったです。

この説明からも、区間の中に要素が含まれているのか、境界が含まれているのかが曖昧に感じました。

Image

参考までにどの回答が正しく、どの回答が誤りだったか教えていただけるとありがたいです。
お手数おかけしますがよろしくお願いしますmm

この文章からは、模範解答が存在し、それを答えることができておらず、減点された、という認識が見受けられます。元の質問には正解不正解はなく、首尾一貫した考え方・コーディングができているかを確認する意図がありました。

首尾一貫していたかいないかという点については、いなかったように思います。一番矛盾していた点は、 target より大きい最小の要素の位置を探そうとしていたにもかかわらず、ループが終了した時点で、区間の中に要素が残っていなかった点です。

その他、言葉の使い方に違和感を感じた点、説明が誤っていると感じた点が数点ありました。

  • [left, right)、leftからright-1までの配列が区間には含まれると思っています。

  • こちらはtargetが存在する可能性のある最大のインデックスの次のインデックスです。

ただ、人によって二分探索の考え方は異なっているようです。上記の考え方は、あくまで自分の考え方に沿って指摘・回答したものです。自分がどのように考えているかを認識・説明することができたうえで、他の人が書いた二分探索を理解できれば十分のようです。

Copy link

Choose a reason for hiding this comment

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

うーん。

要するに、主張するべきことは

  1. 引数の制約はなにか。
  2. ループの不変条件はなにか。
  3. middle の制約はなにか。
  4. 更新によって不変条件が守られるか。
  5. ループの終了条件とその時に欲しいものが見つかっているか。
  6. 有限回で終了するか。
    です。(同じコードでも表現の仕方は複数あります。)

left は "targetが存在する可能性がある最小のインデックス" という表現は、なにかおかしいんですね。
「返すべき値が存在する可能性がある最小のインデックス」だったら、まあいいんですが。

ただ、その約束している内容としては可能性の話をするよりは具体的に否定している内容を書いたほうがいいでしょう。「0 から left - 1 (両端を含む)範囲すべてを開いたとして、それはすべて target 以下である。target と同じ値かもしれないが、target よりも大きい数字は存在しない。」ということですね。

- 時間:O(logN)
- 空間:O(1)
- Upper Bound
- targetを超える最初のindexを返す、そのためreturnで調整している
Copy link

Choose a reason for hiding this comment

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

うーん、まあ、分かっているような気がするんですが、ちょっと自信がもてないので

おかしな値がでると「誰の責任か」が追求される、このギスギスしている職場の話

などを読んでおいてください。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.c15qprmvxkc2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants