问题描述
小S最近在分析一个数组 h1,h2,...,hNh1,h2,...,hN,数组的每个元素代表某种高度。小S对这些高度感兴趣的是,当我们选取任意 kk 个相邻元素时,如何计算它们所能形成的最大矩形面积。
对于 kk 个相邻的元素,我们定义其矩形的最大面积为:
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 个相邻元素中的最小值乘以 kk。现在,小S希望你能帮他找出对于任意 kk,R(k)R(k) 的最大值。
测试样例
样例1:
输入:
n = 5, array = [1, 2, 3, 4, 5]
输出:9
样例2:
输入:
n = 6, array = [5, 4, 3, 2, 1, 6]
输出:9
样例3:
输入:
n = 4, array = [4, 4, 4, 4]
输出:16
问题背景
小S正在分析一个数组,数组中的每个元素代表某种高度。他感兴趣的是,当我们选取任意 k 个相邻元素时,如何计算它们所能形成的最大矩形面积。具体来说,对于 k 个相邻的元素,我们定义其矩形的最大面积为:
R(k)=k×min(h[i],h[i+1],...,h[i+k−1])
即,R(k) 的值为这 k 个相邻元素中的最小值乘以 k。小S希望你能帮他找出对于任意 k,R(k) 的最大值。
思路分析
-
问题理解:
- 我们需要找到数组中任意 k 个相邻元素所能形成的最大矩形面积。
- 这个矩形的面积由 k 个元素中的最小值决定,乘以 k。
-
数据结构选择:
- 为了高效地找到每个元素作为最小值时所能形成的最大矩形面积,我们可以使用单调栈。
- 单调栈可以帮助我们快速找到每个元素作为最小值时的左右边界。
-
算法步骤:
- 使用两个数组
left和right分别记录每个元素作为最小值时的左边界和右边界。 - 通过单调栈计算
left和right数组。 - 遍历数组,计算每个元素作为最小值时所能形成的矩形面积,并更新最大面积。
- 使用两个数组
直接方法
一种直接但低效的方法是对每一个可能的 K 进行两层循环,寻找每一段 K 个元素中的最小值并计算面积,最后比较得到最大值。这种方法的时间复杂度为 O(n^2),当数组长度较大时会非常耗时。
单调栈优化
为了提高效率,可以采用单调栈的方法来解决这个问题。通过维护一个递增的栈,我们可以快速找到每个高度作为矩形的高时,所能向左和向右扩展的最大范围。这样就能在 O(n) 的时间内计算出所有可能的最大矩形面积。
具体步骤如下:
- 使用单调栈计算每个位置的左侧边界,即该位置向左延伸直到遇到比它矮的第一个柱子的位置。
- 类似地,计算每个位置的右侧边界。
- 对于每个位置,用高度乘以左右边界的距离来计算可能的最大矩形面积。
- 在所有计算出的面积中选择最大的一个。
代码分析
int solution(int n, std::vector<int>& A) {
定义了一个名为 solution 的函数,接受一个整数 n 和一个整数向量 A 作为参数,返回一个整数。
int max_area = 0;
初始化一个变量 max_area 为 0,用于存储最终的最大矩形面积。
std::vector<int> left(n), right(n);
定义两个向量 left 和 right,大小均为 n,分别用于存储每个位置的左侧边界和右侧边界。
std::vector<int> stack;
定义一个栈 stack,用于辅助计算边界。
// 初始化栈和计算左侧边界
for (int i = 0; i < n; ++i) {
while (!stack.empty() && A[stack.back()] >= A[i]) {
stack.pop_back();
}
left[i] = stack.empty() ? 0 : stack.back() + 1;
stack.push_back(i);
}
这段代码用于计算每个位置的左侧边界: 外层循环遍历数组 A 的每个元素。 内层循环检查栈顶元素的高度是否大于或等于当前元素的高度,如果是,则弹出栈顶元素。 如果栈为空,说明当前元素左边没有更矮的柱子,因此 left[i] 设为 0;否则,left[i] 设为栈顶元素的下一个位置。 将当前索引 i 压入栈中。
// 清空栈并计算右侧边界
stack.clear();
for (int i = n - 1; i >= 0; --i) {
while (!stack.empty() && A[stack.back()] >= A[i]) {
stack.pop_back();
}
right[i] = stack.empty() ? n : stack.back();
stack.push_back(i);
}
这段代码用于计算每个位置的右侧边界: 首先清空栈。 外层循环从数组 A 的最后一个元素开始向前遍历。 内层循环检查栈顶元素的高度是否大于或等于当前元素的高度,如果是,则弹出栈顶元素。 如果栈为空,说明当前元素右边没有更矮的柱子,因此 right[i] 设为 n;否则,right[i] 设为栈顶元素的位置。 将当前索引 i 压入栈中。
// 计算最大矩形面积
for (int i = 0; i < n; ++i) {
max_area = std::max(max_area, A[i] * (right[i] - left[i]));
}
这段代码用于计算每个位置的最大矩形面积: 遍历数组 A 的每个元素。 计算以当前高度 A[i] 为高的矩形面积,宽度为 right[i] - left[i]。 更新 max_area 为当前计算的面积和已知最大面积中的较大者。
return max_area;
返回计算得到的最大矩形面积。
心得分析
-
单调栈的应用:
- 单调栈是一种非常强大的数据结构,特别适用于解决需要找到某个元素的左右边界的问题。
- 通过单调栈,我们可以在 O(n) 的时间复杂度内计算出每个元素的左右边界,从而高效地解决问题。
-
代码优化:
- 在实际应用中,我们可以进一步优化代码,例如减少不必要的数组访问和计算。
- 通过合理的数据结构选择和算法设计,我们可以显著提高代码的性能和可读性。
-
问题抽象:
- 通过抽象问题,我们可以将复杂的问题简化为一系列简单的步骤,从而更容易找到解决方案。
- 在解决类似问题时,我们可以借鉴已有的算法和数据结构,从而更快地找到解决方案。
通过这道题目,我们不仅学会了如何使用单调栈解决最大矩形面积问题,还加深了对数据结构和算法设计的理解。