LeetCode 84. 柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
方法一:暴力解法
具体来说就是:依次遍历柱形的高度,对于每一个高度分别向两边扩散,求出以当前高度为矩形的最大宽度多少。
左边看一下,看最多能向左延伸多长,找到大于等于当前柱形高度的最左边元素的下标;
右边看一下,看最多能向右延伸多长;找到大于等于当前柱形高度的最右边元素的下标。
对于每一个位置,我们都这样操作,得到一个矩形面积,求出它们的最大值。
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
size = len(heights)
res = 0
for i in range(size):
left = i
cur_height = heights[i]
while left > 0 and heights[left - 1] >= cur_height:
left -= 1
right = i
while right < size - 1 and heights[right + 1] >= cur_height:
right += 1
max_width = right - left + 1
res = max(res, max_width * cur_height)
return res
时间复杂度:O(N^2),这里 N 是输入数组的长度。
空间复杂度:O(1)。
方法二:分治法
矩形的面积取决于区间中最小的高度,因此考虑以拥有最小高度的点作为分治中心,将区域分为三份。
因此区间中的最大面积为
max(
最小高度 * 区间长度(整个区间),
最小高度左侧的最大面积(区间左半部分),
最小高度右侧的最大面积(区间右半部分)
)
对应的时间复杂度为O(nlogn)。
但是如果无法均分,例如数组降序的情况下,分治会退化为O(n²)
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
def divide(left, right):
if left > right:
return 0
min_i = left
for i in range(left, right + 1):
if heights[i] < heights[min_i]:
min_i = i
return max((right - left + 1) * heights[min_i], divide(left, min_i - 1), divide(min_i + 1, right))
return divide(0, len(heights) - 1)
方法三:以空间换时间,用到的数据结构是栈
解析
只要是遇到了当前柱形的高度比它上一个柱形的高度严格小的时候,一定可以确定它之前的某些柱形的最大宽度,并且确定的柱形宽度的顺序是从右边向左边。因为无法矩形向右扩展.
这个现象告诉我们,在遍历的时候需要记录的信息就是遍历到的柱形的下标,它一左一右的两个柱形的下标的差就是这个面积最大的矩形对应的最大宽度。
在确定一个柱形的面积的时候,除了右边要比当前严格小,其实还蕴含了一个条件,那就是左边也要比当前高度严格小。左边也无法扩展.
我们在缓存数据的时候,是从左向右缓存的,我们计算出一个结果的顺序是从右向左的,并且计算完成以后我们就不再需要了,符合后进先出的特点。因此,我们需要的这个作为缓存的数据结构就是栈。
需要考虑两种特殊的情况:
弹栈的时候,栈为空;
遍历完成以后,栈中还有元素;
为此可以我们可以在输入数组的两端加上两个高度为 0 (或者是 0.5,只要比 1 严格小都行)的柱形,可以回避上面这两种分类讨论。
有了这两个柱形:
左边的柱形(第 1 个柱形)由于它一定比输入数组里任何一个元素小,它肯定不会出栈,因此栈一定不会为空;
右边的柱形(第 2 个柱形)也正是因为它一定比输入数组里任何一个元素小,它会让所有输入数组里的元素出栈(第 1 个哨兵元素除外)
这里栈对应到高度,呈单调增加不减的形态,因此称为单调栈
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
heights = [0] + heights[:] + [0]
n = len(heights)
stack = []
max_area = 0
for i in range(n):
curr = heights[i]
while stack and curr < heights[stack[-1]]:
temp = stack.pop()
h = heights[temp]
w = i - stack[-1] - 1
max_area = max(max_area, h * w)
stack.append(i)
return max_area
时间复杂度:O(N),输入数组里的每一个元素入栈一次,出栈一次。
空间复杂度:O(N),栈的空间最多为 N。
LeetCode 85. 最大矩形
给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
输入:
[
["1","0","1","0","0"],
["1","0","1","1","1"],
["1","1","1","1","1"],
["1","0","0","1","0"]
]
输出: 6
解析:这一题的算法本质上和84题一样,对每一行都求出每个元素对应的高度,这个高度就是对应的连续1的长度,然后对每一行都更新一次最大矩形面积。那么这个问题就变成了Largest Rectangle in Histogram。本质上是对矩阵中的每行,均依次执行84题算法。
class Solution:
def maximalRectangle(self, matrix: List[List[str]]) -> int:
m = len(matrix)
if m == 0:
return 0
ans = 0
n = len(matrix[0])
height = [0] * n
for i in range(m):
for j in range(n):
if matrix[i][j] == '0':
height[j] = 0
else:
height[j] = height[j] + 1
ans = max(ans, self.maxrect(height))
return ans
def maxrect(self, height):
height = [0] + height + [0]
n = len(height)
stack = []
res = 0
for i in range(n):
while stack and height[i] < height[stack[-1]]:
temp = stack.pop()
h = height[temp]
w = i - stack[-1] - 1
res = max(res, h * w)
stack.append(i)
return res
LeetCode 739. 每日温度
请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73]
,你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]
。
解析:本题也是用单调栈,栈中存储索引,如果当前温度比栈顶元素的温度高,那么栈顶元素对应的那一天还需要i-stack[-1]
天升高温度,如果当前温度比栈顶元素温度低,那么把该天数放入栈中.
class Solution:
def dailyTemperatures(self, T: List[int]) -> List[int]:
n = len(T)
stack = []
res = [0] * n
for i in range(n):
while stack and T[i] > T[stack[-1]]:
res[stack[-1]] = i - stack[-1]
stack.pop()
stack.append(i)
return res