-
Notifications
You must be signed in to change notification settings - Fork 0
300. Longest Increasing Subsequence #33
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,333 @@ | ||
| # Step1 | ||
|
|
||
| かかった時間:7min | ||
|
|
||
| 計算量: | ||
| nums.length=Nとして | ||
|
|
||
| 時間計算量:O(NlogN) | ||
|
|
||
| 空間計算量:O(N) | ||
|
|
||
| ```python | ||
| import bisect | ||
|
|
||
|
|
||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| lis = [] | ||
| for num in nums: | ||
| if not lis or lis[-1] < num: | ||
| lis.append(num) | ||
| continue | ||
|
|
||
| i = bisect.bisect_left(lis, num) | ||
| lis[i] = num | ||
|
|
||
| return len(lis) | ||
| ``` | ||
| 思考ログ: | ||
| - 直近で解いて覚えていた | ||
| - 最初はこの解法を受け入れるのに時間がかかった気がする | ||
| - 例えば、[10, 11, 12, 1, 2]だと、変数lisは | ||
| - [10] | ||
| - [10, 11] | ||
| - [10, 11, 12] | ||
| - [1, 11, 12] | ||
| - [1, 2, 12] | ||
| - 大事なのは大小関係なので、それを崩さないように挿入していけば、2つの候補を同時に管理できる | ||
| - 名前がlisじゃないという議論があったと思うが、一度ロムってから考えよう、、 | ||
|
|
||
| ボトムアップDP | ||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| lis = [1] * len(nums) | ||
| for i in range(1, len(nums)): | ||
| for j in range(i): | ||
| if nums[j] < nums[i]: | ||
| lis[i] = max(lis[i], lis[j] + 1) | ||
|
|
||
| return max(lis) | ||
| ``` | ||
| - 一番分かりやすいし思いつく解法では | ||
| - 命名の問題が同様にある | ||
|
|
||
| # Step2 | ||
|
|
||
| 講師役目線でのセルフツッコミポイント: | ||
| - 命名関連の選択肢は再度考えた方がいい | ||
| - 名前を工夫する | ||
| - コメントで補足する | ||
|
|
||
| 参考にした過去ログなど: | ||
| - https://github.com/kazukiii/leetcode/pull/32 | ||
| - Seg木 | ||
| - https://github.com/seal-azarashi/leetcode/pull/28 | ||
| - Javaの標準ライブラリの二分探索メソッドの返り値が悩ましい | ||
| - https://github.com/Ryotaro25/leetcode_first60/pull/34 | ||
| - https://github.com/Yoshiki-Iwasa/Arai60/pull/46 | ||
| - 命名、lisに関して | ||
| - https://github.com/fhiyo/leetcode/pull/32 | ||
| - BIT、Seg木 | ||
| - BITはロジックを読んどく、Seg木は実装してみよう | ||
| - https://github.com/SuperHotDogCat/coding-interview/pull/28 | ||
| - https://github.com/sakupan102/arai60-practice/pull/32 | ||
| - https://github.com/goto-untrapped/Arai60/pull/18 | ||
| - https://github.com/YukiMichishita/LeetCode/pull/7 | ||
| - https://github.com/shining-ai/leetcode/pull/31 | ||
| - BIT、Seg木 | ||
| - https://github.com/hayashi-ay/leetcode/pull/27 | ||
|
|
||
|
|
||
| セグ木 | ||
| ```python | ||
| IDENTITY_ELEMENT = 0 | ||
|
|
||
| class SegTree: | ||
| def __init__(self, n: int): | ||
| leaf_n = 1 | ||
| while leaf_n < n: | ||
| leaf_n *= 2 | ||
| self.leaf_n = leaf_n | ||
| self.tree = [0] * (2 * leaf_n - 1) | ||
|
|
||
| def _convert_to_tree_index(self, i: int) -> int: | ||
| return i + self.leaf_n - 1 | ||
|
|
||
| def query(self, l: int, r: int): | ||
| ''' | ||
| note: | ||
| 右半開区間でquery範囲を指定する(x ∈ [l, r)) | ||
| ''' | ||
| l = self._convert_to_tree_index(l) | ||
| r = self._convert_to_tree_index(r) | ||
|
|
||
| result = IDENTITY_ELEMENT | ||
|
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. IDENTITY_ELEMENT, 定数 (本当は変数ですが) にする必要ありますかね?自分はそのまま
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. 演算によって単位元が変わるので、こんな形式にしたのですが、このSegTreeクラスは汎用的に設計ができてる訳でもないので、なんだか中途半端なことになっていると思います。 |
||
| while l < r: | ||
| # if l is left child | ||
| if l % 2 == 0: | ||
| result = max(result, self.tree[l]) | ||
| # if r-1 is right child | ||
| if r % 2 == 0: | ||
| result = max(result, self.tree[r - 1]) | ||
| l = (l - 1) // 2 | ||
| r = (r - 1) // 2 | ||
|
Comment on lines
+107
to
+115
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.
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.
やっていることは、、説明がややこしいですね。。何か具体例を考えるのがいいかもです。 配列の長さが7のケース(葉が4つ)で考えます。 numsから4(圧縮後)が出てきたとします。 この時、3[1] 4[2], 5[3]の各々について確認するのではなく、1[1, 2] , 5[3]だけ確認すれば効率がいいということで、そのような候補の探索を始めるのですが、例えば左端の3[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. 圧縮した要素の集合は1-indexedになってますが0-indexedですかね?(じゃないとコードにある木のindexへの変換が上手くいかない気がします) これって一般にはqueryの引数としてlが0以外も来ることを想定していると思いますが、lが0より大きい場合って変なことになったりしませんか?上の具体例だとたとえば たとえば下の図に書いたような [1,3)の区間に対するクエリ (
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 = 4 で r = 4 が来るとどこまでループが回りますかね。l = 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. あ、これ、まずなんで訳が分からなくなるかというと、l, r という整数に2つの情報、つまり、「どこ」から「どこ」までのセグメントであるか、という情報を載せていて、(segment - 1) // 2 という整数の上での操作と、セグメントの上での意味が遠く見えるからです。 さて、segment(l, width=1) で、セグメントの範囲の値が手に入るとしましょう。 効率を考えないと result = 0
for i in range(l, r):
result = max(result, segment(i, 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. ところで、これだと効率が悪いので速くしたいのですよね。 result = 0
width = 1
while l < r:
# segment(f(l), width) と segment(g(r), width) で result 更新
# l, r を微修正
width *= 2次、l と r の不変条件は、なんでしょうか。[l, r) の範囲は未計算ということですね。 result = 0
while l < r:
result = max(result, segment(l, 1))
l += 1
result = max(result, segment(r - 1, 1))
r -= 1
# 最後一回は2回行われることがあるので本当は判定必要だけれども今回は max だからまあいいでしょう。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. さて、l が width * 2 で割れるときには、segment の計算を これくらいの情報が陽にあれば、読めるコードになると思います。
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 以前にも”意図と操作の距離”についてコメントを頂いていました。
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. |
||
|
|
||
| return result | ||
|
|
||
| def update(self, i: int, val: int) -> None: | ||
| i = self._convert_to_tree_index(i) | ||
| self.tree[i] = val | ||
|
|
||
| # get parent_index | ||
| i = (i - 1) // 2 | ||
| while i >= 0: | ||
| self.tree[i] = max(self.tree[2 * i + 1], self.tree[2 * i + 2]) | ||
| i = (i - 1) // 2 | ||
|
Comment on lines
+123
to
+127
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. while (i := (i - 1) // 2) >= 0:
self.tree[i] = max(self.tree[2 * i + 1], self.tree[2 * i + 2])一応、こうも書けそうですね。見づらくなるギリギリのラインな気もしますが |
||
|
|
||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| def compress(nums: list[int]) -> list[int]: | ||
| num_to_compressed_num = {} | ||
| for i, num in enumerate(sorted(set(nums))): | ||
| num_to_compressed_num[num] = i | ||
|
|
||
| compressed_num = [num_to_compressed_num[num] for num in nums] | ||
| return compressed_num | ||
|
|
||
| compressed_num = compress(nums) | ||
|
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 = len(set(compressed_num)) | ||
| st = SegTree(n) | ||
| for i, num in enumerate(compressed_num): | ||
| res = st.query(0, num) | ||
|
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. resより良い命名があると思います。ものとしてはnumより小さい値で終わるLISのうち最長のものですね。 |
||
| st.update(num, res + 1) | ||
|
|
||
| return st.tree[0] | ||
| ``` | ||
| 思考ログ: | ||
| - この問題に対してoverkillなのは分かっているが、皆さんが結構実装されているのでこの機会にお勉強してみた | ||
| - シンプルなセグ木の理解自体はそんなにかからなかったが、寧ろこの問題での使い方の部分で躓いた(それはセグ木を理解できていな(ry) | ||
|
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. うーん、この overkill という表現にとても違和感があります。 「必要よりも高度な道具を使って解決する」くらいの意図だと思うのですが、ここでいう高度とはどういうことでしょうか。 これは、アルゴリズムやデータ構造が、なんらかの高度さの順に並んでいるという感覚がなければ出てこない表現です。 セグメントツリーは、確かに「使えることに気が付きにくいが、計算量を落とせる場合が極めて稀にあり、短時間で書けるくらい単純である」という意味で、プログラミングコンテストに向いているため、競技プログラミング同好会時代にも出てきていました。
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. ありがとうございます。 今回私が「overkill」と感じたのは、セグ木のような方法を用いずとも、もっと素直で万人が思いつくであろう方法が他にあるだろう、さらにそれを使うことで劇的に計算量が改善されるならまだしも、そうでないならoverkill(やり過ぎ)だろう、という感覚だったのだと思います。 ご指摘のアルゴリズムやデータ構造に順序が入っているのでは、という点に関してはなんとも言えないところがあり、こんなの誰かから聞かないと一生思い付かないな、みたいなアイデアに関しては高度なもの、という感覚は少なからずあります。ただそのようなものを知っていることが偉い、という感覚はない(はず)と思っています。 |
||
| - 考え方を簡単にまとめておく | ||
| - 座標圧縮 | ||
| - 大小の情報だけあればいいので大きさ順に番号を振り直す | ||
| - ```query``` で自分より小さい数字で終わる最長増加部分列(LIS)を探す | ||
| - ```update```で自分の担当部分のLISの情報を更新する | ||
| - ```query```で返ってきた結果を一つ大きくすればいい | ||
| - 自分の先祖の情報も全部更新する | ||
| - 最後にrootの値を取ってくればいい | ||
| - TIPS | ||
| - 木は配列で管理すると扱いやすい | ||
| - (0-indexで考えて)親から子供のインデックスを知りたい時は、親のインデックス * 2 + 1, 親のインデックス * 2 + 2 | ||
| - 子供から親のインデックスを知りたい時は、(子のインデックス - 1) // 2 | ||
| - ビット演算でもできるが、今回は(自分にとっての)分かりやすさを優先した | ||
| - 更新と区間の情報取得が入り乱れている時に有用 | ||
| - 更新が一回だけの場合などは累積和のロジックなど、前処理をしておけば良いが、道中更新があると困る | ||
| - 任意の区間を二分木のノードに分割できるのがミソ | ||
| - 端の扱いなどをちゃんと意識して書いていかないと容易にバグる | ||
|
|
||
| # Step3 | ||
|
|
||
| かかった時間:1min | ||
|
|
||
| ```python | ||
| import bisect | ||
|
|
||
|
|
||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| # 長さがindex+1になるような任意の増加部分列を考えた時 | ||
| # それらの最後の要素のうち最小のものを記録するための配列 | ||
| # ex) [4,6], [3,5], [1,2]ならmin(6, 5, 2) = 2が入る | ||
| minimum_last_val_subseq = [] | ||
|
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. 個人的には length_to_min_last_vals という名前にすると思います。配列の添え字に何を指定すると、どのような値が返ってくるかを表そうとしています。ただ、 length と添え字が 1 ずれている点に注意が必要です。 |
||
| for num in nums: | ||
| if not minimum_last_val_subseq \ | ||
| or minimum_last_val_subseq[-1] < num: | ||
| minimum_last_val_subseq.append(num) | ||
|
|
||
| i = bisect.bisect_left(minimum_last_val_subseq, num) | ||
| minimum_last_val_subseq[i] = num | ||
|
Comment on lines
+184
to
+189
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. continue忘れですかね?numをappendしてからnumを二分探索してその位置にnumを挿入していて無駄な気がします。 個人的には常に二分探索する方が好みです。 i = bisect_left(minimum_last_val_subseq, num)
if i >= len(minimum_last_val_subseq):
minimum_last_val_subseq.append(num)
else:
minimum_last_val_subseq[i] = num
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. ありがとうございます。 |
||
|
|
||
| return len(minimum_last_val_subseq) | ||
| ``` | ||
| 思考ログ: | ||
| - コメントで補足する癖をつけよう | ||
| - あんまり冗長にやってもアレなので匙加減と、逆に無駄に不要なコメントを残さないようにも気を付ける | ||
| - GPTに聞いてみた | ||
| > 候補となる変数名 | ||
| > min_last_elements_of_increasing_subsequences | ||
| > 変数が持つ情報をそのまま説明する形で、冗長ですが内容が明確です。長さを意識しつつ正確さを優先したい場合に適しています。 | ||
| > min_ends_of_increasing_subsequences | ||
| > 1の名前を少し短くした形です。「各長さごとの増加部分列の最小の終点(最後の要素)」という意味が伝わりやすいでしょう。 | ||
| > increasing_subseq_min_ends | ||
| > 読みやすさを保ちながら短縮した名前で、PEP 8の命名規則に合致しています。変数の用途や内容が概ね分かりやすくなります。 | ||
| > min_last_of_increasing_sublists | ||
| > subsequencesではなくsublist(部分リスト)という名前で少し簡略化していますが、役割は理解しやすいでしょう。 | ||
| > おすすめ | ||
| > min_ends_of_increasing_subsequencesが適切です。この名前は要素が増加部分列の最終要素の最小値であることを明確に示し、長さも扱いやすいです。 | ||
| > もし簡潔さを重視する場合は、increasing_subseq_min_endsも良い選択です。 | ||
| - なお、質問は以下 | ||
| > 今pythonでコードを書いているのですが、変数名の命名で困っています。 | ||
|
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. プロンプトエンジニアリングですね。 |
||
| > この変数Xは配列で、以下のような特徴を持ちます。 | ||
| > - X[i]は、ある数列Yの長さi+1の増加部分列が対応します。 | ||
| > - X[i]にはそのような部分列の最終要素の中で最小のものが入ります。 | ||
| > 例えばY = [1,3,2,4]を考えるとX[1]に対応する増加部分列は[1,3], [1,2], [1,4], [3,4], [2,4]となり、min(3, 2, 4, 4, 4) = 2なのでX[1]=2となります。 | ||
| > Xとして適切な名前を提案してください。なお、pep8の命名規則を守ってください。 | ||
|
|
||
| # Step4 | ||
|
|
||
| セグ木を再度実装 | ||
| ```python | ||
| class SegTree: | ||
| def __init__(self, n: int): | ||
| leaf_n = 1 | ||
| while leaf_n < n: | ||
| leaf_n *= 2 | ||
| self.leaf_n = leaf_n | ||
| # 1-indexed tree | ||
| self.tree = [0] * (2 * leaf_n) | ||
|
|
||
| def update(self, seg_i: int, val: int) -> None: | ||
| assert 0 <= seg_i < self.leaf_n, f'seg_i is out of index. seg_i: {seg_i}' | ||
|
|
||
| tree_i = seg_i + self.leaf_n | ||
|
Comment on lines
+231
to
+233
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. 1行で済むような処理なので関数に切り出さない選択をしましたが、このインデックス変換の計算は、何をしているかぱっと見では分かりにくいので、関数として切り出すのも良いなと思いました。 |
||
| self.tree[tree_i] = val | ||
| while tree_i > 1: | ||
| tree_i //= 2 | ||
| self.tree[tree_i] = max(self.tree[2 * tree_i], self.tree[2 * tree_i + 1]) | ||
|
|
||
| def query(self, start_seg_i: int, end_seg_i: int) -> int: | ||
| assert 0 <= start_seg_i < self.leaf_n, f'start_seg_i is out of index. start_seg_i: {start_seg_i}' | ||
| assert start_seg_i <= end_seg_i <= self.leaf_n, f'end_seg_i is out of index. end_seg_i: {end_seg_i}' | ||
|
|
||
| tree_l = start_seg_i + self.leaf_n | ||
| tree_r = end_seg_i + self.leaf_n | ||
|
Comment on lines
+243
to
+244
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. tree_lやtree_rという変数名を見てindexだとは思えない気がします。
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. いい名前が思いつきませんでした。。 |
||
| result = 0 | ||
| while tree_l < tree_r: | ||
| if tree_l % 2: | ||
| result = max(result, self.tree[tree_l]) | ||
| tree_l += 1 | ||
| if tree_r % 2: | ||
| tree_r -= 1 | ||
| result = max(result, self.tree[tree_r]) | ||
| tree_l //= 2 | ||
| tree_r //= 2 | ||
|
|
||
| return result | ||
|
|
||
| def get_maximum_val_of_all_segments(self): | ||
| return self.tree[1] | ||
|
|
||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| def compress(nums: list[int]) -> list[int]: | ||
| num_to_compressed_num = {} | ||
| for i, num in enumerate(sorted(set(nums))): | ||
| num_to_compressed_num[num] = i | ||
|
|
||
| compressed_num = [num_to_compressed_num[num] for num in nums] | ||
| return compressed_num | ||
|
|
||
| compressed_num = compress(nums) | ||
| n = len(set(compressed_num)) | ||
| st = SegTree(n) | ||
| for i, num in enumerate(compressed_num): | ||
| res = st.query(0, num) | ||
|
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. resとは何でしょうか?
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. resultの略で横着しました。。 |
||
| st.update(num, res + 1) | ||
|
|
||
| return st.get_maximum_val_of_all_segments() | ||
|
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. st.query(0, n)でもう一度計算するのも無駄かなと思い、この処理を加えました。 |
||
| ``` | ||
| 思考ログ: | ||
| - 以下アップデートしました。 | ||
| - 単位元は直接0で初期化するように変更(汎用的な実装でもないので) | ||
| - 計算をシンプルにするために内部のtreeの配列は1-indexedにした | ||
| - セグメントの値や区間指定は0-indexedのままにしている | ||
| - 使う側は内部の1-indexedを意識しないでいいように(treeにはアクセスしないでいいように)敢えて```get_maximum_val_of_all_segments```を実装した | ||
| - ```query```のバグを修正 | ||
| - 道中計算が必要になったlとrは微調整をしないといけないことに気づいていなかった | ||
| - セグメント[0], [1]で[1]を採用した場合、次は[2]以降を考えないといけないのにその親[0, 1]に遷移してしまっていた | ||
| - ビット演算ver | ||
| - ビット演算と(完全)二分木のノード移動をまとめておく(1-indexed配列) | ||
| - 自分のインデックスを```i```として | ||
| - 親:```i >> 1``` | ||
| - 兄弟:```i ^ 1``` | ||
| - 左の子:```i << 1 | 0``` | ||
| - 右の子:```i << 1 | 1``` | ||
| - なぜ1との排他的論理和で兄弟? | ||
| - 自分が偶数のインデックスの時は相手は一つ大きい奇数、自分が奇数のインデックスの時は相手は一つ小さい偶数 | ||
| - 自分が偶数の時最下位ビットは0、^1を取ると最下位ビットは1になる(一つ大きい奇数) | ||
| - 自分が奇数の時最下位ビットは1、^1を取ると最下位ビットは0になる(一つ小さい偶数) | ||
| ```python | ||
| def update(self, seg_i: int, val: int) -> None: | ||
| assert 0 <= seg_i < self.leaf_n, f'seg_i is out of index. seg_i: {seg_i}' | ||
|
|
||
| tree_i = seg_i + self.leaf_n | ||
| self.tree[tree_i] = val | ||
| while tree_i > 1: | ||
| tree_i >>= 1 | ||
| self.tree[tree_i] = max(self.tree[tree_i << 1 | 0], self.tree[tree_i << 1 | 1]) | ||
|
|
||
| def query(self, start_seg_i: int, end_seg_i: int) -> int: | ||
| assert 0 <= start_seg_i < self.leaf_n, f'start_seg_i is out of index. start_seg_i: {start_seg_i}' | ||
| assert start_seg_i <= end_seg_i <= self.leaf_n, f'end_seg_i is out of index. end_seg_i: {end_seg_i}' | ||
|
|
||
| tree_l = start_seg_i + self.leaf_n | ||
| tree_r = end_seg_i + self.leaf_n | ||
| result = 0 | ||
| while tree_l < tree_r: | ||
| if tree_l & 1: | ||
| result = max(result, self.tree[tree_l]) | ||
| tree_l += 1 | ||
| if tree_r & 1: | ||
| tree_r -= 1 | ||
| result = max(result, self.tree[tree_r]) | ||
| tree_l >>= 1 | ||
| tree_r >>= 1 | ||
|
|
||
| return result | ||
| ``` | ||
|
|
||
| ```python | ||
| ``` | ||
|
|
||
| 思考ログ: | ||

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.
step3で書いたつもりでした。