Skip to content

Conversation

@TORUS0818
Copy link
Owner

right = len(nums)
while left < right:
middle = (left + right) // 2
if is_index_between_min_val_and_last_val(middle):

Choose a reason for hiding this comment

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

一行なら関数使わなくても良いような気がしました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

if nums[i] <= nums[-1]と書かれた場合に、それがtrueの場合は何を意味するのか、個人的には直感的でないと思ったので関数名に情報を入れました。

if nums[i] > nums[i + 1]:
return nums[i + 1]

raise Exception('unreachable.')

Choose a reason for hiding this comment

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

これは何が目的ですか?

Copy link
Owner Author

Choose a reason for hiding this comment

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

seal-azarashi/leetcode#21 (comment)

以前、上記のような議論がありまして、ここに到達しないことが一目で分かるようにこのようにしました。

Choose a reason for hiding this comment

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

勉強になりました、ありがとうございます。
Javaだとコンパイルされるので必須ということだと思うんですが、Pythonでもたしかにそのほうが分かりやすいですね。

Copy link

Choose a reason for hiding this comment

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

Unreachable なところに raise を書くことですが、ありかもしれませんが、私はそこまで肯定的ではないです。結構微妙なところだと思います。

まず、一般的に、dead code は避けるものです。
また、Python の場合、返り値があって到達する場合はあってもなくても同じだが return None を書き、到達しない場合は書かないことで、unreachable かの意図は表現されるはずです。
それでは弱く、よほど気になるならば、コメントを一つ書いておくくらいが適切かもしれません。

Copy link
Owner Author

Choose a reason for hiding this comment

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

コメントありがとうございます。

正直、深い考えがあってこの書き方をしているわけではなかったので、今後気をつけようと思います。

else:
right = middle

return nums[0] if len(nums) == left else nums[left]

Choose a reason for hiding this comment

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

私なら、この除外処理はループの前に
if nums[0]<=nums[-1]:
return nums[0]
とします

求めるべき最小値とそれより右の要素をfalse、それより左の要素をtrueとした時、is_index_between...は最初のリストが真に回転されてる場合はそのtrue/falseと一致して、最初からソートされてる場合には違うものになるからです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。
はい、分けて考えるのも一つだと思います。

return nums[left]
```
思考ログ:
- 35.Search Insert Positionと同じ感じで、半開区間で書いてみた
Copy link

Choose a reason for hiding this comment

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

この「半開区間」もなかなか微妙で、何を探しているのかに対して意味が違うし、さらに左右の差もあるんですよ。

右が開いているのだとしても、
[0, 1, 2, 3, 4] に対して、2 を探しているのか、初めて2以上になる境界を探しているのかで、
前者ならば right は 3 で終了っぽいが、後者ならば、境界をその右側で表現して 2 で終了としたりします。

なので、とりあえず、半開と書いて通じた気になっているのよく分からないのですね。

Copy link

Choose a reason for hiding this comment

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

たとえば、理情の先輩方(同期同士)でも同じ半開区間と呼んでいてこんな感じ。左右のどっちを開いているかも違います。

cafelier (kinaba)
https://topcoder-g-hatena-ne-jp.jag-icpc.org/cafelier/20120703/1341324546.html

  int L = 0;
  int R = 10_0000_0000;
  while( R-L > 1 )
  {
     int C = (L+R)/2;
     (query(C) ? R : L) = C;
  }
  return R;

mametter
https://mametter.hatenablog.com/entry/20161117/p1

  l, r = 0, a.size
  while l < r
    m = l + (r - l) / 2
    if a[m] < c
      l = m + 1
    else
      r = m
    end
  end
  l

Copy link

Choose a reason for hiding this comment

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

まず何を探しているのでしょうか。
たとえば、
[0, 0, 1, 1, 2, 2] で 1 を探すのだったら何が欲しいのか。
2が欲しい場合、3が欲しい場合、4が欲しい場合、2-3どれかが欲しい場合。
あたりがあります。
それと、[0, 2] で 1 を探すのだったら何が欲しいのか。
-1 やエラーなどが欲しい場合。1 が欲しい場合。

Python の bisect_left は (2, 1)。bisect_right は (4, 1)。
Java の binarySearch は (2-3, ~insertion point)
C++ の upper_bound は (4, 1)。lower_bound は (2, 1)。

その上で、値なり境界なりをどう表現するのか、あることが確定したほうかないことが確定したほうか、境界ならば、多くは境界の右側でしょうが。だいたいここに4通りくらいあります。

Copy link

Choose a reason for hiding this comment

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

left = -1 から始まるパターンは、ここではあんまり見ませんね。ただ、そういう方法もあります。

Copy link
Owner Author

Choose a reason for hiding this comment

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

この「半開区間」もなかなか微妙で、何を探しているのかに対して意味が違うし、さらに左右の差もあるんですよ。

こちらはすみません、左が閉じていることを前提にした書き方になってました。
正しくは右半開区間ですね。

右が開いているのだとしても、
[0, 1, 2, 3, 4] に対して、2 を探しているのか、初めて2以上になる境界を探しているのかで、
前者ならば right は 3 で終了っぽいが、後者ならば、境界をその右側で表現して 2 で終了としたりします。

確かに思考のプロセスのコメントから、大事な部分が抜けているように思います。

色々な人の過去ログを読んで、以前よりは解像度が高くなった気はしているのですが、まだ自信が持てないので
もう少し詳細に考えていることを書いてみます。
お手数ですが、違和感がある部分をご指摘いただけると幸いです。

(以下は、色々と設定の仕方があると思いますが、今回はこのような方法で探索すると私が決めました)

まず、今回は最小値があるインデックスを探しています。
leftは探索範囲の左側でその場所を含みます。
rightは探索範囲の右側でその場所を含みません。

ピボット(middle)が指す要素が条件を満たすかどうかを判定していきます。
条件は、”最小値があるインデックスか、最小値があるインデックスより右のインデックス”にしました。
探索範囲の補集合について補足すると、leftより左(leftを含まない)は上記の条件を満たさない集合、right以上は上記の条件を満たす集合となっています。

探索範囲を上記条件を満たすように狭めていきます。
ピボットを(left + right) // 2で決めているので、毎回left <= middle < rightとなり探索区間は狭くなります。
left < rightの条件でループしているので、最終的にleft == rightとなってループを抜け、以下のような形で止まります。
(F, F, ..., F, [T), T, T, ..., T)

Copy link
Owner Author

Choose a reason for hiding this comment

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

設定を変えて、最小値があるインデックスの一つ前の場所を境界として返す場合は、一番右にあるFのインデックスを取ってくるように設計すれば良いと思います。

開区間で書くと以下のような感じでしょうか。
下のケースでは、left=2, right=3で止まり、left以下はF、right以上はTとなっています。
rightを取ってくれば、上で考えた最小値のインデックスを取得する問題に対応します。

nums = [3, 4, 5, 1, 2]
# nums = [1, 2, 3, 4, 5]

left = -1
right = len(nums)
while right - left > 1:
    middle = (left + right) // 2
    if nums[middle] <= nums[-1]:
        right = middle
    else:
        left = middle

print(left, nums[left]) 

Copy link
Owner Author

@TORUS0818 TORUS0818 Mar 16, 2025

Choose a reason for hiding this comment

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

left = -1 から始まるパターンは、ここではあんまり見ませんね。ただ、そういう方法もあります。

実は恥ずかしながら、この取り組みを始める前までは、(discordでもたまに出てくる)めぐる式2分探索という方法を(一応理解したつもりで)テンプレート化して脳死状態で2分探索を書いておりました。。

ただ、-1でインデックスアクセス出来てしまうのが何となく気持ち悪くなり、最近は右半開区間で書くことが多くなりました。


かかった時間:21min

計算量:nums.length=Nとして
Copy link

Choose a reason for hiding this comment

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

細かい点ですが、 N = num.length の順番のほうが違和感が少なく感じました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。
Nを定義しているわけですから確かに左に来て欲しいですね。

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.

5 participants