-
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?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # 問題へのリンク | ||
| [Find Minimum in Rotated Sorted Array - LeetCode](https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/) | ||
|
|
||
| # 言語 | ||
| 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`に置き換える。 | ||
| - 大文字はPEP8に反する。大文字は定数に使うべき。 | ||
| - 1文字の変数名はPEP8に反する | ||
| - `L`, `R`が市民権を得ているのはatcoderだけ | ||
|
|
||
|
|
||
| # 次に解く問題の予告 | ||
| - [Evaluate Division - LeetCode](https://leetcode.com/problems/evaluate-division/description/) | ||
| - [Subsets - LeetCode](https://leetcode.com/problems/subsets/) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # | ||
| # @lc app=leetcode id=153 lang=python3 | ||
| # | ||
| # [153] Find Minimum in Rotated Sorted Array | ||
| # | ||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def findMin(self, nums: list[int]) -> int: | ||
| def is_index_value_leq_to_last(index: int) -> bool: | ||
| if index < 0 or len(nums) <= index: | ||
| return False | ||
| return nums[-1] >= nums[index] | ||
|
|
||
| left = -1 | ||
| right = len(nums) - 1 | ||
| while (right - left) > 1: | ||
| mid = (right + left) // 2 | ||
| if is_index_value_leq_to_last(mid): | ||
| right = mid | ||
| else: | ||
| left = mid | ||
|
|
||
| return nums[right] | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # | ||
| # @lc app=leetcode id=153 lang=python3 | ||
| # | ||
| # [153] Find Minimum in Rotated Sorted Array | ||
| # | ||
|
|
||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def findMin(self, nums: list[int]) -> int: | ||
| if not nums: | ||
| raise ValueError("nums must have at least one element.") | ||
| elif len(nums) <= 2: | ||
| return min(nums) | ||
|
|
||
| L = 0 | ||
| R = len(nums) - 1 | ||
| # no rotation | ||
| if nums[L] <= nums[R]: | ||
| return nums[L] | ||
|
|
||
| # nums[R] < nums[L] always holds | ||
| while (R - L) > 1: | ||
| mid = (R + L) // 2 | ||
| if nums[mid] < nums[R]: | ||
| R = mid | ||
| # nums[L] < nums[mid]: | ||
| else: | ||
| L = mid | ||
|
|
||
| # R-L = 1, nums[L]: largest, and nums[R]: smallest | ||
| return nums[R] | ||
|
|
||
|
|
||
| # @lc code=end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # | ||
| # @lc app=leetcode id=153 lang=python3 | ||
| # | ||
| # [153] Find Minimum in Rotated Sorted Array | ||
| # | ||
|
|
||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def findMin(self, nums: list[int]) -> int: | ||
| if not nums: | ||
| raise ValueError("nums must have at least one element.") | ||
| elif len(nums) <= 2: | ||
| return min(nums) | ||
|
|
||
| left = 0 | ||
| right = len(nums) - 1 | ||
| # Array is not rotated (Already sorted in ascending order) | ||
| if nums[left] <= nums[right]: | ||
| return nums[left] | ||
|
|
||
| # nums[right] < nums[left] always holds during binary search | ||
| while (right - left) > 1: | ||
| mid = left + (right - left) // 2 | ||
| if nums[mid] <= nums[right]: | ||
| right = mid | ||
| # nums[left] <= nums[mid] | ||
| else: | ||
| left = mid | ||
|
|
||
| # nums[left]: largest, and nums[right]: smallest | ||
| return nums[right] | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # | ||
| # @lc app=leetcode id=153 lang=python3 | ||
| # | ||
| # [153] Find Minimum in Rotated Sorted Array | ||
| # | ||
|
|
||
|
|
||
| # @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 commentThe reason will be displayed to describe this comment to others. Learn more. step1, step2ではnumsが空配列のときのケアをしているがstep3では行っておらずIndexErrorを投げ得る点は少し気になりました。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. レビューありがとうございます。 |
||
| left = 0 | ||
| right = len(nums) - 1 | ||
| # case 1. already sorted | ||
| if nums[left] <= nums[right]: | ||
| return nums[left] | ||
|
|
||
| # 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 commentThe reason will be displayed to describe this comment to others. Learn more. Python で整数は overflow しないのでこれは不要ですね
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. レビューありがとうございます。結論その通りだと思います。 |
||
| if nums[left] < nums[mid]: | ||
| left = mid | ||
| else: | ||
| right = mid | ||
|
|
||
| return nums[right] | ||
|
|
||
|
|
||
| # @lc code=end | ||
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 文と
が、以下の二分探索で正しい解が出力できないためハックとして入れたものに感じられました。
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]:と修正してあげる必要があります。二分探索について考える場合は、
あたりを考えるとよいと思います。
この問題については過去に多くのレビューコメントが付けられています。「二分探索」で Discord サーバーを検索することをおすすめします。
なお私の過去のコメントの中には、二分探索について十分に理解していなかった頃に書いたものがあります。小田さんのコメントを中心に読まれることをおすすめします。
もし余力があれば、以下のソースコードが二分探索についてどのような考えに基づいて書かれているか、説明してみてください。
補足となりますが、上記のソースコードは練習用に書いたコードです。他のソフトウェアエンジニアは上記のような考え方や書き方は、あまりしないと思います。実務では上記のような考え方や書き方はしないほうがよいと思います。
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.
ですので (-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.
ありがとうございます!