Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions grind75_hard/09_84_Largest Rectangle in Histogram/level_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# 総当たり(TLE)
# O(N^3)
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
max_rectangle = [0] * len(heights)
for i in range(len(heights)):
for j in range(i + 1, len(heights) + 1):
rectangle = min(heights[i:j]) * (j - i)
max_rectangle[i] = max(max_rectangle[i], rectangle)
return max(max_rectangle)


# 最小値の判定を効率化した総当たり(TLE)
# O(N^2)
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
max_rectangle = [0] * len(heights)
for i in range(len(heights)):
min_height = heights[i]
for j in range(i, len(heights)):
min_height = min(min_height, heights[j])
rectangle = min_height * (j - i + 1)
max_rectangle[i] = max(max_rectangle[i], rectangle)
return max(max_rectangle)


# 最小値で左右に分割してそれぞれを比較(TLE)
# O(NlogN)
# ソートされている時はO(N^2)
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
def calc_area(left, right):
if left >= right:
return 0
min_value = float("inf")
for i in range(left, right):
if min_value < heights[i]:
continue
min_value = heights[i]
min_index = i
area = min_value * (right - left)
max_area = max(
area, calc_area(left, min_index), calc_area(min_index + 1, right)
)
return max_area

return calc_area(0, len(heights))


# 最小値をsegment treeで管理して効率化
# O(NlogN)
class SegmentTree:
def __init__(self, nums):
self.n = len(nums)
self.tree = [None] * (2 * self.n)
for i, num in enumerate(nums):
self.tree[self.n + i] = (num, i)
for i in range(self.n - 1, 0, -1):
self.tree[i] = min(self.tree[i * 2], self.tree[i * 2 + 1])

def query(self, left, right):
left += self.n
right += self.n
min_value_index = (float("inf"), 0)
while left < right:
if left % 2 == 1:
min_value_index = min(min_value_index, self.tree[left])
left += 1
if right % 2 == 1:
right -= 1
min_value_index = min(min_value_index, self.tree[right])
left //= 2
right //= 2
return min_value_index[0], min_value_index[1]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return min_value_index
でもいいですか?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

問題ないです。無駄に分けてました。
return min_value_indexの方がシンプルで分かりやすいです。



class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
segment_tree = SegmentTree(heights)

def calc_area(left, right):
if left >= right:
return 0
min_value, min_index = segment_tree.query(left, right)
area = min_value * (right - left)
max_area = max(
area, calc_area(left, min_index), calc_area(min_index + 1, right)
)
return max_area

return calc_area(0, len(heights))
21 changes: 21 additions & 0 deletions grind75_hard/09_84_Largest Rectangle in Histogram/level_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 左から高さを確認していき、高さが減少するタイミングでその高さより高い場合の面積を計算する。
# [3, 5, 2, 6]の場合、2に到達した時に高さが減少するので、それより左側にある2より高い3と5の面積を計算する。
# 5のとき: 5 * 1 = 5
# 3のとき: 3 * 2 = 6
# 端まで到達したら、計算されなかった要素で計算する。
# 2のとき: 2 * 4 = 6
# 6のとき: 6 * 1 = 6
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
max_area = 0
stack = []
for end in range(len(heights)):
start = end
while stack and stack[-1][1] > heights[end]:
start, height = stack.pop()
max_area = max(max_area, height * (end - start))
stack.append([start, heights[end]])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stack[-1][1] == heights[end]
なら足さなくてもいいでしょうか。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同値はここで処理しなくても、次のwhile stack:のループで処理されるのでどちらでも良いのかと思いました。

while stack:
start, height = stack.pop()
max_area = max(max_area, height * (len(heights) - start))
return max_area
16 changes: 16 additions & 0 deletions grind75_hard/09_84_Largest Rectangle in Histogram/level_3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
max_area = 0
stack = []
for end in range(len(heights)):
start = end
while stack and stack[-1][1] > heights[end]:
start, height = stack.pop()
area = height * (end - start)
max_area = max(max_area, area)
stack.append((start, heights[end]))
while stack:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

番兵を使う、すなわち、stack = [] の前に、heights.append(0) という手があるかもしれません。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

番兵を使った方が最後の後処理がなくなって分かりやすいです。
stack = [-1]を置く方法もあるかと思いましたが、こちらは分かり辛いのでやめておきました。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一応、入力を破壊的に変更すると、呼んだ人がびっくりする可能性があるので、それを指摘されたら考えましょう。

  1. 用途によっては何もしないのも一つです。
  2. あるいは、こう書かないのも一つです。
  3. 入力をコピーしちゃうのは一つです。コピーのコストについて聞かれたら、Python であることなどいろいろ勘案してコストは小さいと見積もるということです。
  4. 使い終わったら pop して元に戻すのも一つです。ただし、その場合は、スレッド並列性との兼ね合いがあります。

マルチスレッドで起きる諸々といえば、
The Deadlock Empire
https://deadlockempire.github.io/#menu

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1,2の選択肢は盲点でした。
並列処理も全く考えていませんでした。

The Deadlock Empireとても分かりやすいですね。
後ほど全て見ておきたいと思います。

start, height = stack.pop()
area = height * (len(heights) - start)
max_area = max(max_area, area)
return max_area
63 changes: 63 additions & 0 deletions grind75_hard/09_84_Largest Rectangle in Histogram/level_4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# segment treeを書き直した
# 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])

# 最上段から下がっていき、[begin, end)の最小値を取得
# [node_begin, node_end)が現在のノードの区間
def query(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(begin, end, node * 2, node_begin, node_middle)
right_min = self.query(begin, end, node * 2 + 1, node_middle, node_end)
return min(left_min, right_min)


class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
segment_tree = SegmentTree(heights)

def calc_area(begin, end):
if begin >= end:
return 0
min_value, min_index = segment_tree.query(begin, end)
area = min_value * (end - begin)
max_area = max(
area, calc_area(begin, min_index), calc_area(min_index + 1, end)
)
return max_area

return calc_area(0, len(heights))


# stackを使った解法に番兵を利用
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
max_area = 0
heights.append(0)
stack = []
for end in range(len(heights)):
start = end
while stack and stack[-1][1] >= heights[end]:
start, height = stack.pop()
max_area = max(max_area, height * (end - start))
stack.append([start, heights[end]])
return max_area