题目解析
题目要求我们计算数组中所有可能的 连续子数组长度为 k 的最大矩形面积,公式为 R(k)=k×min(h[i],h[i+1],...,h[i+k−1])R(k)=k×min(h[i],h[i+1],...,h[i+k−1])。最终我们需要找出所有可能的 R(k)R(k) 中的最大值。
解题思路
暴力解法
-
思路:对于每一个可能的 kk(子数组长度),遍历所有可能的起点 ii,计算长度为 kk 的子数组的最小值,进而计算面积 k×mink×min。
- 遍历 kk 的范围:从 1 到 nn。
- 对于每个 kk,找出所有可能的子数组,计算其最小值和面积。
- 更新全局最大面积。
-
时间复杂度:
- 外层循环 kk 为 O(n)O(n)。
- 内层计算所有子数组起点 ii 为 O(n)O(n)。
- 每个子数组的最小值计算 O(k)O(k)。
- 总复杂度约为 O(n3)O(n3),适用于小规模数据。
优化解法
通过优化计算最小值的过程,降低时间复杂度:
-
滑动窗口法:
- 使用双端队列维护当前窗口的最小值索引,从而快速获取最小值。
- 滑动窗口的移动和最小值维护的复杂度为 O(n)O(n)。
- 时间复杂度降低至 O(n2)O(n2)。
-
单调栈法:
- 利用单调栈计算柱状图的最大矩形面积,等价于题目问题。
- 此方法的时间复杂度进一步优化为 O(n)O(n)。
图解
以数组 [5, 4, 3, 2, 1, 6] 为例:
-
当 k=1k=1:所有单个高度的矩形面积是 [5, 4, 3, 2, 1, 6],最大值为 6。
-
当 k=2k=2:子数组有 [5,4], [4,3], [3,2], [2,1], [1,6],面积分别为 [8, 6, 4, 2, 2],最大值为 8。
-
当 k=3k=3:子数组有 [5,4,3], [4,3,2], [3,2,1], [2,1,6],面积分别为 [9, 6, 3, 6],最大值为 9。
- 重复上述步骤找到所有可能面积的最大值。
代码实现
暴力解法
def solution(n, array):
max_area = 0
# 遍历所有可能的 k 值
for k in range(1, n + 1):
for i in range(n - k + 1):
# 计算 k 个相邻元素的最小值
min_height = min(array[i:i + k])
# 计算矩形面积
area = k * min_height
# 更新最大面积
max_area = max(max_area, area)
return max_area
优化解法(滑动窗口)
from collections import deque
def solution(n, array):
max_area = 0
for k in range(1, n + 1):
min_deque = deque() # 双端队列
for i in range(n):
# 移出窗口左侧
if min_deque and min_deque[0] < i - k + 1:
min_deque.popleft()
# 维护单调递增队列
while min_deque and array[min_deque[-1]] >= array[i]:
min_deque.pop()
min_deque.append(i)
# 如果窗口满了,计算面积
if i >= k - 1:
max_area = max(max_area, k * array[min_deque[0]])
return max_area
在解决这个问题的过程中,涉及以下知识点:
1. 数组操作
- 切片:通过
array[i:i+k]获取数组的子数组。 - 遍历:遍历数组的所有元素或所有子数组的起始位置。
2. 暴力枚举
- 逐步增加范围:从最小的子数组(长度为 1)到完整数组(长度为 n),依次计算所有可能的矩形面积。
- 时间复杂度分析:通过逐层嵌套循环,分析程序效率。
3. 优化算法
-
滑动窗口:
- 利用双端队列维护当前窗口的最小值。
- 通过窗口的移动来避免重复计算,提高效率。
- 双端队列的操作(插入、删除、更新)时间复杂度为 O(1)O(1)。
-
单调栈:
- 单调栈是一种特殊的数据结构,可以快速处理连续数据的特定属性(如柱状图中的最大矩形问题)。
- 栈中保持元素单调递增或递减,以快速查找高度的左右边界。
- 在当前问题中,用单调栈解决最大矩形问题,把每个元素视为柱状图中的柱子。
4. 数学公式
-
面积公式:矩形面积为
宽度 × 高度,其中高度是子数组的最小值,宽度是子数组长度 kk。 -
动态维护最小值:
- 在暴力解法中,直接调用 Python 内置函数
min()。 - 在滑动窗口中,动态维护窗口内的最小值索引。
- 在单调栈中,利用高度递增(或递减)性质找到最小值的左右边界。
- 在暴力解法中,直接调用 Python 内置函数
5. 时间复杂度分析
-
暴力解法:
- 三重循环,复杂度为 O(n3)O(n3)。
-
滑动窗口:
- 双层循环,复杂度降低至 O(n2)O(n2)。
-
单调栈:
- 每个元素最多被入栈和出栈一次,复杂度为 O(n)O(n)。
6. 代码结构
-
函数封装:
- 定义函数
solution(n, array),传入长度 nn 和数组array。
- 定义函数
-
模块化测试:
- 使用
if __name__ == "__main__":测试函数,验证代码正确性。
- 使用
-
可维护性:
- 将暴力解法、滑动窗口法、单调栈法分成独立模块,便于理解和扩展。
7. 边界处理
-
特殊情况:
- 数组长度为 1:直接返回单个元素。
- 数组元素相等:返回 n×h[0]n×h[0]。
-
哨兵技巧:
- 在单调栈方法中,添加一个高度为 0 的哨兵,简化边界处理逻辑。
8. 常用数据结构
-
双端队列(deque) :
- 从
collections模块中导入,用于维护滑动窗口中的最小值。 - 支持快速的插入和删除操作。
- 从
-
栈:
- 用于单调栈法中存储元素索引,便于计算高度和宽度的关系。
9. Python内置函数
min():计算数组中的最小值(暴力解法中使用)。max():更新全局最大矩形面积。append()和pop():操作双端队列和栈。
总结
- 暴力解法适合小规模数据,逻辑简单但效率低。
- 滑动窗口优化部分重复计算,时间复杂度降低至 O(n2)O(n2)。
- 单调栈法结合柱状图思路,直接计算最大矩形面积,效率最高 O(n)O(n)。