-
Notifications
You must be signed in to change notification settings - Fork 0
Solve 153_find_minimum_in_rotated_sorted_array_medium #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| def findMin(self, nums: list[int]) -> int: | ||
| if not nums: | ||
| raise ValueError("nums must have at least one element.") | ||
| elif len(nums) <= 2: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
この elif 文と
# no rotation
if nums[L] <= nums[R]:
return nums[L]が、以下の二分探索で正しい解が出力できないためハックとして入れたものに感じられました。
L は a[n-1] と a[0] との境界より常に左にあり、 R は a[n-1] と a[0] との境界より常に右にあるという設定だと思います。その場合、 L = -1、 R = len(nums) と初期化することで、ローテーションしていない場合に 0 を出力することができるようになるはずです。ただし、 if nums[mid] < nums[R]: は if nums[mid] <= nums[-1]: と修正してあげる必要があります。
二分探索について考える場合は、
- left と right が要素の位置を表しているのか?要素と要素の間の境界の位置を表しているのか?
- left の位置の要素または境界を、区間に含めるか?含めないか?
- right の位置の要素または境界を、区間に含めるか?含めないか?
- 区間を狭めるとき、 mid の位置の要素または境界を区間の中に含める/含めないためには、いくつ mid に足せばよいか/から引けばよいか?
- 最後の状態で left と right の位置関係はどうなっているか?
- 最後に度の値を返せばよいか?
あたりを考えるとよいと思います。
この問題については過去に多くのレビューコメントが付けられています。「二分探索」で Discord サーバーを検索することをおすすめします。
なお私の過去のコメントの中には、二分探索について十分に理解していなかった頃に書いたものがあります。小田さんのコメントを中心に読まれることをおすすめします。
もし余力があれば、以下のソースコードが二分探索についてどのような考えに基づいて書かれているか、説明してみてください。
class Solution:
def findMin(self, nums: list[int]) -> int:
if not nums:
raise ValueError("nums must have at least one element.")
L = -1
R = len(nums) + 1
while L + 2 < R:
mid = (R + L) // 2
if nums[mid] <= nums[-1]:
R = mid + 1
else:
L = mid
return nums[L + 1]補足となりますが、上記のソースコードは練習用に書いたコードです。他のソフトウェアエンジニアは上記のような考え方や書き方は、あまりしないと思います。実務では上記のような考え方や書き方はしないほうがよいと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
レビューありがとうございます。
確かに前処理が少々煩雑で、ハックしたように(≒エッジケースに引っかかったから足したように)見えてしまったり、可読性を下げてしまったりしてしまいました。
私は基本的にはleft=0, right=len(nums)-1からスタートして閉区間[left, right]を探索する代わりに、面倒なケースは前処理ですべてearly returnする方針で二分探索を解いてきました。しかし、もう少し問題ごとに柔軟にわかりやすい方針で解くよう意識したいと思います。
ソースコードの二分探索について
くださったソースコードの二分探索は、以下の考えで書かれたと思います。
leftとrightは要素の位置を表しているleft,rightの位置の要素を区間に含めない- 添え字を開区間
(left, right)で探索している- 添え字は
0,1,...,len(nums)-1なので、(-1, len(nums))スタート
- 添え字は
- 添え字を開区間
- 求める値の添え字は
leftとrightに挟まれた値 - そのため、最後に返すべき値は
nums[left+1] = nums[right-1]になる
開区間で二分探索を考えたことは無かったので、はじめはとても読みづらいと感じましたが、一度わかってしまえばすっと読めました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
添え字は0,1,...,len(nums)-1なので、(-1, len(nums))スタート
R = len(nums) + 1ですので (-1, len(nums) + 1) スタートとなっています。もう一度読み直してみていただけますか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
確かにその通りですね。
では、left, rightは要素と要素の間の境界の位置を開区間で表していると思います。
境界の位置は0, 1, ..., len(nums) なので、それを開区間で表すと(-1, len(nums)+1)となります。
境界の位置iに対応する要素の値はnums[i]として最後に返すべき値はnums[left+1] = nums[right-1]になる
(L=len(nums)-1になってしまうとまずいのではないかとも思いましたが、mid=len(nums)-1のときにはif nums[mid] <= nums[-1]に引っかかるので大丈夫、と理解しました。)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
良いと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ありがとうございます!
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def findMin(self, nums: list[int]) -> int: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
step1, step2ではnumsが空配列のときのケアをしているがstep3では行っておらずIndexErrorを投げ得る点は少し気になりました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
レビューありがとうございます。
たしかにその通りですね。失念していました。
| # case 2. rotated | ||
| # find min using binary search. nums[left] > nums[right] always holds | ||
| while right - left > 1: | ||
| mid = left + (right - left) // 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python で整数は overflow しないのでこれは不要ですね
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
レビューありがとうございます。結論その通りだと思います。
Pythonでオーバーフローしないことは知っていたのですが、right+leftが64bit整数型の範囲を超えて、多倍長整数の演算に切り替わる場合にはパフォーマンスが著しく落ちるのではないかと思って一応このように書いていました。
しかし計測してみたところ、それほど差が出ませんでした。むしろ足し算の算術演算の回数が小さくなる(right+left)//2の方が良いまでありそうですね。
num_trials = 50_000_000
big_int = pow(2, 64)
time_start = time.time()
for _ in range(num_trials):
_ = big_int + big_int
elapsed = time.time() - time_start
print(f"{elapsed=}") # 2.343sec
small_int = pow(2, 10)
time_start = time.time()
for _ in range(num_trials):
_ = small_int + small_int
elapsed = time.time() - time_start
print(f"{elapsed=}") # 2.320sec
問題へのリンク
Find Minimum in Rotated Sorted Array - LeetCode
言語
Python
問題の概要
回転された昇順ソート配列から最小値を見つける。配列の回転とは、要素を一つずつ右にずらす操作を指す。例えば、
[3, 4, 5, 1, 2]は[1, 2, 3, 4, 5]を回転させた結果である。本問では時間計算量が
O(log(n))であることが求められる。自分の解法
二分探索を用いて、回転された配列の最小値を見つける。配列の中央の要素と端の要素を比較し、どちら側に最小値が存在するかを判断する。
元の配列を
a0<a1<a2<...<anとすると、回転された配列はak+1< ak+2<...<an > a0<a1<...<akのような形になる。この性質を利用して、二分探索を行う。leftとrightをぞれぞれ配列の左端と右端のインデックスとして初期化し、中央の要素を計算する。中央の要素が右端の要素より大きい場合、最小値は右側にあるため、leftをmidに更新する。逆に、中央の要素が右端の要素以下の場合、最小値は左側または中央にあるため、rightをmidに更新する。この操作を繰り返し、最終的にrightが最小値のインデックスとなる。二分探索では常に
nums[left] > nums[right]が成り立つように、leftとrightの更新を行う。O(log(n))O(1)step2
L,Rをそれぞれleft,rightに置き換える。L,Rが市民権を得ているのはatcoderだけ次に解く問題の予告