-
Notifications
You must be signed in to change notification settings - Fork 0
35. Search Insert Position #2
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,177 @@ | ||
| # 35. Search Insert Position <!-- omit in toc --> | ||
|
|
||
| ## 1. 問題 | ||
|
|
||
| ### 1.1. リンク | ||
|
|
||
| <https://leetcode.com/problems/search-insert-position/description/> | ||
|
|
||
| ### 1.2. 問題概要 (閲覧制限のある問題の場合のみ) | ||
|
|
||
| ## 3. ステップ1 | ||
|
|
||
| ### 3.1. コード | ||
|
|
||
| 標準ライブラリ使うなら; | ||
|
|
||
| ```python | ||
| import bisect | ||
| class Solution: | ||
| def searchInsert(self, nums: List[int], target: int) -> int: | ||
| return bisect.bisect_left(nums, target) | ||
|
|
||
| ``` | ||
|
|
||
| 再実装するなら; | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def searchInsert(self, nums: List[int], target: int) -> int: | ||
| if len(nums) == 0 or target <= nums[0]: | ||
| return 0 | ||
| if nums[-1] < target: | ||
| return len(nums) | ||
| if nums[-1] == target: | ||
| return len(nums) - 1 | ||
| smaller, greater = 0, len(nums) - 1 | ||
| while greater - smaller > 1: | ||
| mid = smaller + (greater - smaller) // 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 では整数はほとんどの場合でオーバーフローしないため、 mid = (smaller + greater) // 2 で十分だと思います。 |
||
| if nums[mid] == target: | ||
| return mid | ||
| if nums[mid] < target: | ||
| smaller = mid | ||
| else: | ||
| greater = mid | ||
| return smaller + 1 | ||
|
|
||
| ``` | ||
|
|
||
| 以下の自然言語の説明を念頭に置いていた; | ||
|
|
||
| 1. `target` より真に小さいやつと真に大きいやつが居ることを確認して(最初の3つのif文) | ||
| 1. いなければ答えはすぐ返せる. | ||
| 2. 空配列のときは新たにその要素が挿入されると考えると0を返すのが自然に思えた. | ||
| 2. そいつらの間に求めたいものが居るんだから,間に1個以上要素がある限り探す.(while文) | ||
| 1. 真ん中を調べてみて,もし`target`が見つかったら即座に答えを返す. | ||
| 2. 真に小さいか真に大きいかで,対応するインデックスの変数`smaller`, `greater`に記録する. | ||
| 3. `target`がいなかったんだから,真に小さい要素の次に`target`は挿入されるべきです,と報告する. | ||
|
|
||
| ### 3.2. 時間・空間計算量 | ||
|
|
||
| 引数`nums` のサイズを$N$として, | ||
| 時間$O(\log N)$, 空間 $O(1)$. | ||
| $N$があまりに巨大なら`smaller`, `greater`, `mid` あたりのサイズがでかくなって空間$O(\log N)$に. | ||
|
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. N が大きくなろうが、
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. コメントありがとうございます. 意図としては,int型の変数に対するCPythonの内部表現のメモリサイズについてでした.適宜リファレンスなりするべきでしたね.以下に簡単な検証結果も併せて記載します. リファレンスCPythonの多倍長整数の表現を見てみますと,
また,実装上は, 検証さて,検証コードを動かしてみました; 検証コードimport math
def memory_profile(x: int):
power = int(math.log10(x))
bit_length = x.bit_length()
num_of_32bit_integers = -(-bit_length // 30)
array_size_in_bytes = 4 * num_of_32bit_integers
print(
f"x = 10 ** {power:>5}, x.bit_length= {bit_length:>6}, #32bit_integers = {num_of_32bit_integers:>4}, 32bit_integer_array = {array_size_in_bytes:>5}, {x.__sizeof__() = }"
)
one = 1
print(f"{one.__sizeof__() = }")
for i in range(5):
too_big = 10 **(4 * 10**i)
memory_profile(too_big)出力結果は以下です;実行結果としても,多倍長整数は30bitに収まらない範囲の整数に対処するために,適宜メモリを大きく確保していることが分かります. 以上のように,内部表現では30bit整数を用いるため,「 |
||
|
|
||
| ## 4. ステップ2 | ||
|
|
||
| ### 4.1. コード | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def searchInsert(self, nums: List[int], target: int) -> int: | ||
| lo, hi = 0, len(nums) | ||
| while lo < hi: | ||
| mid = lo + (hi - lo) // 2 | ||
| if nums[mid] < target: | ||
| lo = mid + 1 | ||
| else: | ||
| hi = mid | ||
| return lo | ||
| ``` | ||
|
|
||
| ### 4.2. 講師陣のコメントとして想定されること | ||
|
|
||
| ### 4.3. 他の人のコードを読んで考えたこと | ||
|
|
||
| 標準ライブラリ`bisect.py`の実装 | ||
|
|
||
| ```python | ||
| def bisect_left(a, x, lo=0, hi=None, *, key=None): | ||
| """Return the index where to insert item x in list a, assuming a is sorted. | ||
|
|
||
| The return value i is such that all e in a[:i] have e < x, and all e in | ||
| a[i:] have e >= x. So if x already appears in the list, a.insert(i, x) will | ||
| insert just before the leftmost x already there. | ||
|
|
||
| Optional args lo (default 0) and hi (default len(a)) bound the | ||
| slice of a to be searched. | ||
| """ | ||
|
|
||
| if lo < 0: | ||
| raise ValueError('lo must be non-negative') | ||
| if hi is None: | ||
| hi = len(a) | ||
| # Note, the comparison uses "<" to match the | ||
| # __lt__() logic in list.sort() and in heapq. | ||
| if key is None: | ||
| while lo < hi: | ||
| mid = (lo + hi) // 2 | ||
| if a[mid] < x: | ||
| lo = mid + 1 | ||
| else: | ||
| hi = mid | ||
| else: | ||
| while lo < hi: | ||
| mid = (lo + hi) // 2 | ||
| if key(a[mid]) < x: | ||
| lo = mid + 1 | ||
| else: | ||
| hi = mid | ||
| return lo | ||
|
|
||
| ``` | ||
|
|
||
| - このいきなりwhile文突入の実装,以前見たっきり忘れていた. | ||
| - `bisect_left` | ||
| 1. $x \le a[i]$なる最小の$i$が欲しくて,$0\le i\le len(a)$ なのと,条件$x < a[i]$が$i$についての単調性を持つのは知ってる. | ||
| - $a[len(a)]$は適当な番兵が入ってるとみなしておく.実際には$lo=hi=len(a)$にはならないので$a[len(a)]$はアクセスされない. | ||
| 2. 求めたい$i$の最小値$lo$と最大値$hi$を管理しよう. | ||
| 3. 単調性を使って今後の探索範囲を半々に分けたいから真ん中取って$mid$へ代入. | ||
| 4. $x \le a[mid]$ なら$i$の最大値$hi$は$mid$で抑えられるね.そうじゃないときは$i$の最小値$lo$は$mid+1$で抑えられるね. | ||
| 5. $lo < hi$ の限りは答えが一つに絞れてないから探索を続けるね. | ||
| - $lo > hi$にはならなそうだし,whileの条件を`lo != hi` にしても動きそうだな〜って思ってLLMに聞いたら以下の2点を指摘され,2つ目は大事なので納得した. | ||
| 1. $lo$, $hi$ は答えの最小値・最大値っていう自然に順序を持つ量だからそう宣言するほうが理解しやすい | ||
| 2. 呼び出し側が $lo > hi$ になるように呼び出した場合でも無限ループにならずに終了できる(正しい答えは返せないけど) | ||
| 6. 最小の$i$が欲しかったんだから$lo$返すね. | ||
| - `bisect_right` | ||
| - `bisect_left` の条件 $x \le a[i]$ が $x < a[i]$ に変わっただけ. | ||
| - 内省;`bisect_left`の子項目にした部分が思い出せないからステップ1みたいな自分が素直にわかる実装をとった | ||
| - `#Note, ~` のコメントについて | ||
| - `__lt__` だけあれば他の演算子オーバーロード用のメソッドなくても動くようにしたほうが良いよね,ソートとかが`<`を採用してるからこっちも併せとけばユーザの負担が少ないよね,という話と理解. | ||
| - コードを書く側としては`bisect_left` を実装するときはそのまま `x <= a[i]` と記述したくなるけど, ユーザに`__le__`の実装までも要求したくないから,自然言語で説明の順序が逆転するけど `a[i] < x` を使うのね. | ||
| - 全順序が成り立つなら `functools.total_ordering` で演算子オーバーロード用のメソッドの量を減らせるよと(ただし遅いらしい) <https://docs.python.org/ja/3/library/functools.html#functools.total_ordering>. | ||
| - `functools.cmp_to_key` との直接の併用はできないのか. | ||
| - `bisect.bisect`って `bisect_right` のエイリアスなんだ.ref. docstring | ||
| - 二分探索後にlistに挿入するケースを考えると後ろに近いインデックスを返すほうが計算量がちょっといいからかな? | ||
| - 二分探索して挿入までやる`bisect.insort`なんてのもあるんだ | ||
|
|
||
| - gt32 さん <https://github.com/5103246/LeetCode_Arai60/pull/39/changes> | ||
| - 色々試行錯誤した後,標準ライブラリ`bisect.py` とほぼ同じ実装に収束してる | ||
| - 空配列のときに`-1`を返すのは関数名と相性悪いな〜と思ったらコメントがついていた. | ||
| - 条件を満たすところと満たさないところが切り替わるポイントが配列中にたかだか1個ある,って見方で思い出したこと; | ||
| - 区間の端点として条件を満たす点と満たさない点を保持し続ければ,それらの間に"条件の真偽かの切り替わるタイミングが1個"の制約をはずしても,そのうちの一つを見つけられる.科学計算で$f(x) = 0$ の根を見つけるときとか. | ||
|
|
||
| ### 4.4. 改善するときに考えたこと | ||
|
|
||
| - 標準ライブラリを使った例と違って,targetと等しいような要素が複数含まれていたときに,それらのインデックスのうちの一つを返す実装になっていることに組んでから気づいた. | ||
| - 問題の条件的にはこのケースはないけど考える. | ||
| - while内で見つかったら早期リターンするのは,書く側は楽でいいけど任意の一つを返す実装になる. | ||
| - 呼び出し側としては 1)`bisect_left`, 2) `bisect_right`, 3) 重複要素については任意の一つを返す,を選べたほうがより嬉しいだろうけど,それでもデフォルトは 1 or 2 にしてよさそう.一意に定まるほうが扱いやすそうなので. | ||
| - 今回の問題は`bisect_left`が求められているので,`bisect_left`を実装しましょう. | ||
|
|
||
| ## 5. ステップ3 | ||
|
|
||
| ステップ2と同じ. | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def searchInsert(self, nums: List[int], target: int) -> int: | ||
| lo, hi = 0, len(nums) | ||
| while lo < hi: | ||
| mid = lo + (hi - lo) // 2 | ||
| if nums[mid] < target: | ||
| lo = mid + 1 | ||
| else: | ||
| hi = mid | ||
| return lo | ||
| ``` | ||
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.
smaller greater を同一の行で定義する必然性はあまり感じられませんでした。 2 行に分けて書いたほうが、読み手にとって読みやすいように思います。