-
Notifications
You must be signed in to change notification settings - Fork 0
Largest Rectangle In Histogram #128
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
Open
tom4649
wants to merge
2
commits into
main
Choose a base branch
from
84.Largest-Rectangle-in-Histogram
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| # 84. Largest Rectangle in Histogram | ||
|
|
||
| ## step1 | ||
| 考えたこと: | ||
|
|
||
| 15mぐらい紙に書いて考えたが分からない -> (Hintがないので)Topicを見る -> monotonic stack というキーワードを見つける -> ある長方形を高さとする場合、左と右の、それよりも低い長方形の位置が分かれば解けることに気が付く -> monotonic stackを使えばこの操作を実現できることに気が付く | ||
|
|
||
| 合計で 40m ほど。 | ||
|
|
||
| 「自分より大きい(or 小さい)最初の要素」を探したいときに単調スタックを使う。 | ||
| 以前にも見たはずだが身についていない。 | ||
|
|
||
| 欲しい条件とは逆のスタックを用いる。今回は次に小さい値が欲しいので、スタックは単調増加。 | ||
|
|
||
| ## step2 | ||
| 少し変更 | ||
|
|
||
|
|
||
| ## 類題 | ||
|
|
||
| 練習として以下を解き直す。 | ||
|
|
||
| https://leetcode.com/problems/next-greater-element-i/description/ | ||
|
|
||
|
|
||
| ```python | ||
| class Solution: | ||
| def nextGreaterElement(self, nums1: list[int], nums2: list[int]) -> list[int]: | ||
| num_to_next_greater = {} | ||
| decreasing_stack = [] | ||
|
|
||
| for n in nums2: | ||
| while decreasing_stack and decreasing_stack[-1] < n: | ||
| previous_smaller = decreasing_stack.pop() | ||
| num_to_next_greater[previous_smaller] = n | ||
| decreasing_stack.append(n) | ||
|
|
||
| return [num_to_next_greater.get(n, -1) for n in nums1] | ||
|
|
||
| ``` | ||
|
|
||
|
|
||
| ## 他の人のコード | ||
|
|
||
| - https://github.com/shining-ai/leetcode/pull/68 | ||
|
|
||
| > 最小値をsegment treeで管理して効率化 | ||
|
|
||
| 区間の最小値を求めれば良いからSegment Treeを用いることができる | ||
|
|
||
| 時間計算量 O(NlogN) | ||
|
|
||
| SegmentTreeは頭で知っているけど自分で書けないデータの一つなので練習する必要がある | ||
|
|
||
|
|
||
| > 一応、入力を破壊的に変更すると、呼んだ人がびっくりする可能性があるので、それを指摘されたら考えましょう。 | ||
|
|
||
| > 用途によっては何もしないのも一つです。 | ||
| > あるいは、こう書かないのも一つです。 | ||
| > 入力をコピーしちゃうのは一つです。コピーのコストについて聞かれたら、Python であることなどいろいろ勘案してコストは小さいと見積もるということです。 | ||
| > 使い終わったら pop して元に戻すのも一つです。ただし、その場合は、スレッド並列性との兼ね合いがあります。 | ||
| > マルチスレッドで起きる諸々といえば、 | ||
| > The Deadlock Empire | ||
| > https://deadlockempire.github.io/#menu | ||
|
|
||
| マルチスレッドでは、関数のローカル変数は共有されないが、グローバル変数、クラス変数、(インスタンスを共有する場合の)インスタンス変数が共有される場合、スレッドセーフではなくなる | ||
|
|
||
| 入力の破壊は意識していたが、マルチスレッドで問題が生じるという意識はなかった。 | ||
|
|
||
| The Deadlock Empire、バグらせるゲームなのか。時間があるときに見てみても良いかも。 | ||
|
|
||
| - https://github.com/yamashita-ki/codingTest/pull/12 | ||
| - https://github.com/Exzrgs/LeetCode/pull/39 | ||
|
|
||
| ## step3 | ||
| 単調スタック、セグメントツリーの解法を書く | ||
|
|
||
| TODO: 遅延評価の実装と応用問題 | ||
|
|
||
|
|
||
| 以下LLM: | ||
|
|
||
| ## セグメントツリー(Segment Tree) | ||
|
|
||
| ## 基本構造と親子関係 | ||
| * **ルートノードのインデックスは `1` から始める** | ||
| * `0` から始めるよりも、親子関係のインデックス計算が非常にシンプルになります。 | ||
| * **ノード間の移動(親子関係)の計算規則** | ||
| * **親ノードへの移動:** `i // 2` (またはビット演算で `i >> 1`) | ||
| * **左の子ノードへの移動:** `i * 2` (またはビット演算で `i << 1`) | ||
| * **右の子ノードへの移動:** `i * 2 + 1` (またはビット演算で `(i << 1) | 1`) | ||
|
|
||
| ## 計算量(Time & Space Complexity) | ||
| * **構築(Build):** $O(N)$ | ||
| * 葉ノードから親ノードへ向かってボトムアップに1回走査するだけで構築可能です。 | ||
| * **クエリ(Query)/ 更新(Update):** $O(\log N)$ | ||
| * 木の高さ(深さ)に比例したステップ数だけで、ピンポイントの更新や区間内の検索が完了します。 | ||
|
|
||
| ## 実装のアプローチ(2つの手法) | ||
| * **再帰型(トップダウン)** | ||
| * ルートから目的の区間に向かって条件分岐しながら掘り下げていく。 | ||
| * **メリット:** 構造の直感性が高く、後に「遅延評価」などの高度な機能を拡張しやすい。 | ||
| * **非再帰型(ボトムアップ)** | ||
| * 最下層の「葉」から、区間の両端を挟み込むように親へと登っていく。 | ||
| * **メリット:** 関数の呼び出しオーバーヘッドがないため、実行速度が最速かつ省メモリ(空間計算量 $O(1)$)。 | ||
| * **注意点:** 奇数偶数のインデックス判定(`begin % 2 == 1` / `end % 2 == 1`)のトリックが必要。 | ||
|
|
||
| ## バグを防ぎ、プロっぽく仕上げる実践ポイント | ||
| * **配列サイズは「元の要素数の4倍($4N$)」確保する** | ||
| * べき乗への拡張を行わない場合でも、サイズを $4N$ 確保しておけば、境界値のバグによる `IndexError` を確実に防ぐことができます。 | ||
| * **ビット演算を活用して高速化する** | ||
| * `// 2` や `* 2`、`% 2 == 1` の代わりに、`>> 1`、`<< 1`、`& 1` などのビット演算を使うと実行速度が向上し、低レイヤーに強いクリーンなコードになります。 | ||
| * **問題の目的に応じて「単位元(初期値)」を正しく設計する** | ||
| * **最小値 (RMQ):** `float("inf")` (無限大) | ||
| * **最大値:** `-float("inf")` または `0` | ||
| * **総和 (Sum):** `0` | ||
| * **総積 (Product):** `1` | ||
| * **さらなる発展:遅延評価(Lazy Propagation)** | ||
| * 「区間の一斉更新」を $O(\log N)$ で行うための発展形。データが本当に必要になるまで下位ノードへの更新をサボる(遅延させる)テクニック。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| class Solution: | ||
| def largestRectangleArea(self, heights: list[int]) -> int: | ||
| increasing_stack: tuple[int, int] = [] # list of (height, num_greater_left) | ||
| largest_area = 0 | ||
| heights.append(0) # sentinel | ||
|
|
||
| for height in heights: | ||
| if not increasing_stack or increasing_stack[-1][0] <= height: | ||
| increasing_stack.append((height, 0)) | ||
| continue | ||
|
|
||
| num_greater_right = 0 | ||
| while increasing_stack and increasing_stack[-1][0] > height: | ||
| height_greater, num_greater_left = increasing_stack.pop() | ||
| largest_area = max( | ||
| largest_area, | ||
| height_greater * (num_greater_left + 1 + num_greater_right), | ||
| ) | ||
| num_greater_right += 1 + num_greater_left | ||
|
|
||
| increasing_stack.append((height, num_greater_right)) | ||
|
|
||
| return largest_area |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| class Solution: | ||
| def largestRectangleArea(self, heights: list[int]) -> int: | ||
| increasing_stack: list[ | ||
| tuple[int, int] | ||
| ] = [] # list of (height, num_greater_left) | ||
| largest_area = 0 | ||
| heights.append(0) # sentinel | ||
|
|
||
| for height in heights: | ||
| num_greater_right = 0 | ||
| while increasing_stack and height <= increasing_stack[-1][0]: | ||
| height_of_rectangle, num_greater_left = increasing_stack.pop() | ||
| largest_area = max( | ||
| largest_area, | ||
| height_of_rectangle * (num_greater_left + 1 + num_greater_right), | ||
| ) | ||
| num_greater_right += num_greater_left + 1 | ||
|
|
||
| increasing_stack.append((height, num_greater_right)) | ||
|
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. increasing_stack: tuple[int, int] = [] # list of (height, num_greater_left)とありますが、pushしているのは、num_greater_rightですね。これで読み手に意図は伝わりそうでしょうか?
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 largest_area | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| class Solution: | ||
| def largestRectangleArea(self, heights: list[int]) -> int: | ||
| heights.append(0) | ||
| stack = [-1] | ||
| max_area = 0 | ||
|
|
||
| for i in range(len(heights)): | ||
| while stack[-1] != -1 and heights[stack[-1]] >= heights[i]: | ||
| h = heights[stack.pop()] | ||
| width = i - stack[-1] - 1 | ||
| max_area = max(max_area, h * width) | ||
|
|
||
| stack.append(i) | ||
|
|
||
| return max_area |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| class Solution: | ||
| def largestRectangleArea(self, heights: list[int]) -> int: | ||
| increasing_stack: list[tuple[int, int]] = [] # (バーの高さ, 左側に広がる自分以上の合計幅)のリスト | ||
| largest_area = 0 | ||
| heights.append(0) # sentinel | ||
|
|
||
| for height in heights: | ||
| num_greater_right = 0 # ポップされるバーから見て右側に広がる自分以上の合計幅 | ||
| while increasing_stack and height <= increasing_stack[-1][0]: | ||
| height_of_rectangle, num_greater_left = increasing_stack.pop() | ||
| largest_area = max( | ||
| largest_area, | ||
| height_of_rectangle * (num_greater_left + 1 + num_greater_right), | ||
| ) | ||
| num_greater_right += num_greater_left + 1 | ||
|
|
||
| increasing_stack.append((height, num_greater_right)) # 回収した右の幅がこのバーの左の幅 | ||
|
|
||
| return largest_area |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| # rootのindexは1 | ||
| # 親ノード: i // 2 | ||
| # 子ノード: (i * 2), (i * 2 + 1) | ||
| class SegmentTree: | ||
| def __init__(self, nums): | ||
| self.size = 1 | ||
| while self.size < len(nums): | ||
| self.size *= 2 | ||
| self.tree = [(float("inf"), 0)] * (self.size * 2) | ||
| # 葉に値をセット | ||
| for i, num in enumerate(nums): | ||
| self.tree[self.size + i] = (num, i) | ||
| # ノードを更新 | ||
| for i in range(self.size - 1, 0, -1): | ||
| self.tree[i] = min(self.tree[i * 2], self.tree[i * 2 + 1]) | ||
|
|
||
| # トップダウン | ||
| def query_recursive(self, begin, end, node=1, node_begin=0, node_end=-1): | ||
| if node_end == -1: | ||
| node_end = self.size | ||
| if node_end <= begin or end <= node_begin: | ||
| return (float("inf"), 0) | ||
| if begin <= node_begin and node_end <= end: | ||
| return self.tree[node] | ||
|
|
||
| node_middle = (node_begin + node_end) // 2 | ||
| left_min = self.query_recursive(begin, end, node * 2, node_begin, node_middle) | ||
| right_min = self.query_recursive( | ||
| begin, end, node * 2 + 1, node_middle, node_end | ||
| ) | ||
| return min(left_min, right_min) | ||
|
|
||
| # ボトムアップ | ||
| def query(self, left, right): | ||
| left += self.size | ||
| right += self.size | ||
| min_node = (float("inf"), 0) | ||
| while left < right: | ||
| if left % 2 == 1: | ||
| # 親ノードは範囲外なので右に移動 | ||
| min_node = min(min_node, self.tree[left]) | ||
| left += 1 | ||
| if right % 2 == 1: | ||
| # 親ノードは範囲外なので左に移動 | ||
| right -= 1 | ||
| min_node = min(min_node, self.tree[right]) | ||
| left //= 2 | ||
| right //= 2 | ||
| return min_node | ||
|
|
||
|
|
||
| class Solution: | ||
| def largestRectangleArea(self, heights: list[int]) -> int: | ||
| segment_tree = SegmentTree(heights) | ||
|
|
||
| def calculate_area_with_minimum_height_between(begin, end): | ||
| if begin >= end: | ||
| return 0 | ||
| # min_height, min_index = segment_tree.query(begin, end) | ||
| min_height, min_index = segment_tree.query_recursive(begin, end) | ||
| area = min_height * (end - begin) | ||
| max_area = max( | ||
| area, | ||
| calculate_area_with_minimum_height_between(begin, min_index), | ||
| calculate_area_with_minimum_height_between(min_index + 1, end), | ||
| ) | ||
| return max_area | ||
|
|
||
| return calculate_area_with_minimum_height_between(0, len(heights)) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
参考:
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.
indexだけを入れておいても幅を計算できるのですね。最初に-1を入れておけば条件分岐もなくなりますね。