问题简化
这道题目要求我们求得在给定的数组中,任意k个相邻元素所能形成的最大的矩形面积,其中,矩形面积是通过最小高度乘以这些元素个数来计算的,即。
关键点:
- 在数组中,选取任意连续的
k个元素。 - 对于每个选定的
k个相邻元素,计算其矩形面积,即这k个元素的最小值乘以k。 - 找出所有可能的
k个元素中的最大矩形面积。
解题思路
对于这道题我们很容易想到使用暴力枚举来解题,即——对每个可能的k,枚举所有可能的连续子数组,计算每个子数组的最小值,然后计算面积。这种思路非常简单且直观,但这种办法的时间复杂度为,如果问题的规模提高,这种办法的可行度很低。
因此,我们需要一个新的解法。根据问题关键点,我们可以知道我们要对连续子数组的最小值进行计算,并且每个子数组的计算可能会重复很多次,由此,我们可以尝试使用栈来加速这个过程。
使用栈来优化寻找每个元素的最大矩形面积:
-
单调栈:通过单调栈来维护每个元素的前后限制,从而避免重复计算每个元素的最小值。
- 左边界:使用栈来记录每个元素左侧第一个比它小的位置
- 右边界:使用栈来记录每个元素右侧第一个比它小的位置
通过单调栈,就可以在 时间内解决掉这个最大矩形面积问题。
代码实现
以下是python的具体实现
1. 初始化
stack = []
max_area = 0
left = [0] * n
right = [0] * n
stack: 用来辅助计算每个元素的左边界和右边界。max_area: 记录最大矩形面积,初始值为 0。left: 一个长度为n的数组,用来存储每个元素的左边界,即比该元素小的第一个位置。right: 一个长度为n的数组,用来存储每个元素的右边界,即比该元素小的第一个位置。
2. 计算每个元素的左边界
for i in range(n):
while stack and array[stack[-1]] >= array[i]:
stack.pop()
left[i] = stack[-1] if stack else -1
stack.append(i)
-
遍历数组:从左到右遍历数组,找到每个元素的左边界。
-
栈的作用:
while stack and array[stack[-1]] >= array[i]: 这个循环的作用是,当栈中的元素比当前元素大或相等时,出栈。因为我们需要找到第一个比当前元素小的元素。left[i] = stack[-1] if stack else -1: 如果栈不为空,则栈顶的元素就是当前元素的左边界;如果栈为空,说明当前元素的左边没有比它小的元素,那么左边界为 -1。stack.append(i): 把当前元素的索引入栈,待后续元素的左边界需要参考它。
3. 计算每个元素的右边界
stack.clear()
for i in range(n - 1, -1, -1):
while stack and array[stack[-1]] > array[i]:
stack.pop()
right[i] = stack[-1] if stack else n
stack.append(i)
-
遍历数组:这次从右到左遍历数组,计算每个元素的右边界。
-
栈的作用:
while stack and array[stack[-1]] > array[i]: 这个循环的作用是,当栈中的元素大于当前元素时,出栈。因为我们需要找到第一个比当前元素小的元素。right[i] = stack[-1] if stack else n: 如果栈不为空,则栈顶的元素就是当前元素的右边界;如果栈为空,说明当前元素的右边没有比它小的元素,那么右边界为n(即数组的长度,代表到数组的末尾)。stack.append(i): 把当前元素的索引入栈,待后续元素的右边界需要参考它。
4. 计算最大矩形面积
for i in range(n):
k = right[i] - left[i] - 1
current_area = k * array[i]
max_area = max(max_area, current_area)
-
计算宽度:对于每个元素
i,它的宽度k是由右边界right[i]和左边界left[i]之间的距离来决定的。宽度公式是right[i] - left[i] - 1。right[i]是第一个比array[i]小的元素的索引。left[i]是第一个比array[i]小的元素的索引。- 宽度是右边界减去左边界再减去 1,因为左右边界的索引本身不包括在矩形内。
-
计算面积:面积
current_area是宽度k乘以高度array[i]。 -
更新最大面积:更新
max_area为当前面积与已有的最大面积之间的较大值。
5. 返回最大矩形面积
return max_area
- 最后,返回
max_area,即最大矩形面积。
总结
这道题不仅锻炼了我对栈的运用(例如,在遇到处理“区间”和“边界”问题时,栈就非常好用了),还教我如何从数据结构的角度来优化解题策略。利用 单调栈 来维护每个元素的“左边界”和“右边界”,转变了我从“穷举所有可能”到“利用数据结构精确高效查找边界”的思路。由此可见,我们要学会分析问题的性质,只有这样,才能更好地解决未来可能遇到的大规模数据和更复杂的算法问题。
补充
什么是单调栈
单调栈(Monotone Stack) :这是一种特殊的栈,它在栈的后进先出规则上,还要求从 栈顶 到 栈底 的元素是单调递增(或者单调递减) 的栈,就是单调栈。
单调递增栈: 满足从栈顶到栈底的元素是单调递增的栈(只有比栈顶元素小的元素才能直接入栈,否则需要先将栈中比当前元素小的元素出栈,再将当前元素入栈)
单调递减栈: 满足从栈顶到栈底的元素是单调递减的栈(只有比栈顶元素大的元素才能直接入栈,否则需要先将栈中比当前元素大的元素出栈,再将当前元素入栈)