-
Notifications
You must be signed in to change notification settings - Fork 0
Create 35. Search Insert Position.md #43
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
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,233 @@ | ||
| ``` | ||
| 範囲を絞っていく、というお題なわけですよね。 | ||
| left = 0 | ||
| right = len(nums) - 1 | ||
| と書いたら、left と right の両端を含む、この範囲にある、ということを考えていますね。 | ||
| 言い換えると、「target はあるとすると、left 以上、right 以下に必ずある」ということです。 | ||
|
|
||
| 「target はあるとすると、10以上10以下にあるんだよねー。」といわれたら、10確認しろよ、ってなりますよね。 | ||
| だから、両端を含む場合は、同じ値でも確認しないといけません。 | ||
|
|
||
| また、middle として、100を選んで、そこになかった場合。そこよりも左か、そこよりも右にあるかが、値次第で分かるわけです。 | ||
|
|
||
| left, right は両端を含むわけですから、99以下にあることか101以上にあることかが分かるわけですね。だから1を足し引きします。 | ||
|
|
||
| middle の選び方は、left <= middle <= right であれば、この議論だとどこでも大丈夫なはずです。区間は、最低1減っていきますから。 | ||
|
|
||
| これが頭にあれば、それほど抵抗なく書けませんか? | ||
|
|
||
| ちなみに、現実的には、たぶん、個数が50個くらい以下ならば、ループで頭から探したほうが速いでしょう。(少なくとも C++ では。) | ||
| 分岐予測との兼ね合いです | ||
|
|
||
| left = 0 | ||
| right = len(nums) | ||
|
|
||
| とすることもできて、そうすると、左は含むが右は含まないつもりで書いているわけですね。left 以上 right 未満。 | ||
| 「target はあるとすると、10以上10未満にあるんだよねー。」といったらそんな数はありません。 | ||
|
|
||
| また、middle として、100を選んで、そこになかった場合。そこよりも左か、そこよりも右にあるかが、値次第で分かるわけです。 | ||
|
|
||
| 要は、100未満にあることか101以上にあることかが分かるわけですね。 | ||
|
|
||
| middle の選び方は、left <= middle < right に変わります。 | ||
|
|
||
| というわけで、注目ポイント次第ですね。開区間、半開区間、閉区間とかいったりします。 | ||
| 個数が50個くらい以下ならば、ループで頭から探したほうが速いでしょう。 | ||
| ``` | ||
|
|
||
| ``` | ||
| コードはいいと思います。 | ||
|
|
||
| これ、どう考えるといいかなあと思っています。 | ||
|
|
||
| n 個要素があると、植木算で n + 1 個の切れ目がありますね。 | ||
|
|
||
| そのうち、どこで切ると、 | ||
| 右はすべて、target <= nums[i] で、 | ||
| 左はすべて、nums[i] < target となるか、ということですね。 | ||
| これを answer とでもしましょう。 | ||
|
|
||
| だから、left と right は実は閉区間で、一致するまで回さないといけませんね。 | ||
|
|
||
| mid = (left + right) // 2 | ||
|
|
||
| とすると、切り捨てられるので、 | ||
| left <= mid < right | ||
| になります。 | ||
|
|
||
| nums[mid] < target | ||
| が判明すると、 | ||
| mid < answer が分かります。 | ||
| 一方、 | ||
| target <= nums[mid] | ||
| が判明すると、 | ||
| answer <= mid | ||
| が分かりますね。 | ||
|
|
||
| この辺、どう考えていますか? | ||
| ``` | ||
|
|
||
| 1. 閉区間 ↔︎ a ≤ x ≤ b ↔︎ left = 0, right = len(nums) - 1 | ||
| 以上、以下の範囲で探索を行うため右端を調査対象に含むべき ↔︎ while left <= right | ||
| 2. 半開区間 ↔︎ a ≤ x < b ↔︎ left = 0, right = len(nums) | ||
| 以上、より小さいの範囲で探索を行うため右端を調査対象に含まない ↔︎ while left < right | ||
|
|
||
| * かなり悩んでしまった。bisect_leftの値に重複がないパターンを実装すべき問題ということには気がついた。 | ||
| * 手で解いてみて、調整方法を検討して一応通せた。 | ||
| * 二分探索を理解できていないことに気がついたので、Discord内の議論を参考に復習した。 | ||
| * 半開区間のleftは、left = rightかつ、重複がなければ挿入されるべき位置に収束する。 | ||
| * まだ、"境界"の感覚がわかっていないので、この後の問題を通して理解したい。 | ||
|
|
||
|
|
||
| ``` | ||
| [0, 1, 3, 5, 6], target = 2 | ||
| nums = [0, 1, 3, 5, 6] (left, right) = (0, 4) | ||
| nums = [0, 1] (left, right) = (0, 2) | ||
| nums = [1, 3] (left, right) = (1, 2) | ||
| ``` | ||
|
|
||
|
|
||
| ### 全探索による解法 (1m52s) | ||
| * 時間計算量: O(N) | ||
| * 空間計算量: O(1) | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def searchInsert(self, nums: List[int], target: int) -> int: | ||
| for i in range(len(nums)): | ||
| if nums[i] >= target: | ||
| return i | ||
|
|
||
| return len(nums) | ||
| ``` | ||
|
|
||
|
|
||
| ### 半開区間による解法 | ||
| ### 1回目 (14m13s) | ||
| * 時間計算量: O(logN) | ||
| * 空間計算量: O(1) | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def searchInsert(self, nums: List[int], target: int) -> int: | ||
|
|
||
| left, right = 0, len(nums) - 1 | ||
|
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. 2 行に分けて書いたほうが読みやすいように感じます。 ただし、今回のコードでは関係ナインドエスが、値を swap する場合は 1 行で書いたほうが読みやすいと思います。 |
||
|
|
||
| while left <= right: | ||
| mid = (left + right) // 2 | ||
| value = nums[mid] | ||
|
|
||
| if value == target: | ||
| return mid | ||
|
|
||
| if value > target: | ||
| right = mid - 1 | ||
|
|
||
| if value < target: | ||
| left = mid + 1 | ||
|
|
||
| return left | ||
| ``` | ||
|
|
||
|
|
||
| ### 2回目 | ||
| * ifの分岐が対称性があるため、if-elif-elseに修正 | ||
|
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. こちらの方が読みやすく感じました。
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. nodechipさん、ありがとうございます。 |
||
| * valueという変数名がわかりづらいこともあるが、nums[mid]のままの方が読みやすいと感じたため修正 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def searchInsert(self, nums: List[int], target: int) -> int: | ||
| left, right = 0, len(nums) | ||
|
|
||
| while left < right: | ||
| mid = (left + right) // 2 | ||
|
|
||
| if nums[mid] == target: | ||
| return mid | ||
| elif nums[mid] < target: | ||
| left = mid + 1 | ||
| else: | ||
| right = mid | ||
|
|
||
| return left | ||
| ``` | ||
|
|
||
|
|
||
| ### 3回目 | ||
| * 2回目から変化なし | ||
| * midが略記の認識がある場合を考慮し、一応middleに。 | ||
| ```python | ||
| class Solution: | ||
| def searchInsert(self, nums: List[int], target: int) -> int: | ||
| left, right = 0, len(nums) | ||
|
|
||
| while left < right: | ||
| middle = (left + right) // 2 | ||
|
|
||
| if nums[middle] == target: | ||
| return middle | ||
| elif nums[middle] < target: | ||
|
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 にしますが、趣味の範囲でしょう。ところで、少し空行が多い気がします。 |
||
| left = middle + 1 | ||
| else: | ||
| right = middle | ||
|
|
||
| return left | ||
| ``` | ||
|
|
||
|
|
||
|
|
||
| ## bisect_leftによる解法 | ||
| * ドキュメント: https://docs.python.org/3/library/bisect.html | ||
| * 実装: https://github.com/python/cpython/blob/cfbdce72083fca791947cbb18114115c90738d99/Lib/bisect.py#L74 | ||
|
|
||
| ### 半開区間 | ||
| ```python | ||
| class Solution: | ||
| def searchInsert(self, nums: List[int], target: int) -> int: | ||
| def _is_sorted(arr: List[int]) -> bool: | ||
| for i in range(1, len(arr)): | ||
| if arr[i - 1] > arr[i]: | ||
| return False | ||
| return True | ||
|
|
||
| def bisect_left(arr: List[int], value: int) -> int: | ||
| if _is_sorted(arr) == False: | ||
|
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. 一応ですが、ここで配列を舐めるので線形時間かかりますね。 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 notにしてますね
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.
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. 計算量が悪いコードも実装が単純だと入力次第では好まれることもありますし、あと速いがバグを埋め込みやすいコードの確認などにも使えますね。 計算量がよいこと自体には価値を見出さないほうがいいです。一方で、計算量から見積もられる「計算にかかる時間」はコードの選択の一つのよく使われる基準ではたしかにあります。しかし、基準の一つでしかありません。
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. Odaさん 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. ここで配列内の全要素見てるので、下で二分探索する意味が無い気がします。
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. ご指摘ありがとうございます。そのとおりですね。元々のbisectになかったので、後から良かれと思って追加しましたが、意味無くなっちゃいますね。以後気をつけます。 |
||
| raise ValueError("Input array is not sorted!") | ||
|
|
||
| left, right = 0, len(arr) | ||
| while left < right: | ||
| mid = (left + right) // 2 | ||
| if arr[mid] >= value: # 等号を外した場合(else側に等号を持っていった場合)、bisect_rightになる。 | ||
| right = mid | ||
| else: | ||
| left = mid + 1 | ||
| return left | ||
|
|
||
| return bisect_left(nums, target) | ||
| ``` | ||
|
|
||
| ### 閉区間 | ||
| ```python | ||
| class Solution: | ||
| def searchInsert(self, nums: List[int], target: int) -> int: | ||
| def _is_sorted(arr: List[int]) -> bool: | ||
| for i in range(1, len(arr)): | ||
| if arr[i - 1] > arr[i]: | ||
| return False | ||
| return True | ||
|
|
||
| def bisect_left(arr: List[int], value: int) -> int: | ||
| if not _is_sorted(arr): | ||
| raise ValueError("Input array is not sorted!") | ||
|
|
||
| left, right = 0, len(arr) - 1 | ||
| while left <= right: | ||
| mid = (left + right) // 2 | ||
| if arr[mid] >= target: | ||
| right = mid - 1 | ||
| else: | ||
| left = mid + 1 | ||
| return left | ||
|
|
||
| return bisect_left(nums, target) | ||
| ``` | ||
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.
さっき出てたのでご覧になったかわかりませんが、これわかりやすかったです
Yoshiki-Iwasa/Arai60#35 (comment)
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.
有難うございます!読みました、分かりやすかったです!