问题描述
小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
Code:
using namespace std;
int solution(int n, int array[]) {
// Edit your code here
int currenth,currentw;
int sum=0;
for(int i=0;i<n;i++){
currenth=array[i];
currentw=1;
for(int j=i-1;j>=0;j--){
if(array[j]>=currenth){
currentw+=1;
}else{
break;
}
}
for(int j=i+1;j<n;j++){
if(array[j]>=currenth){
currentw+=1;
}else{
break;
}
}
sum=max(sum,currenth*currentw);
}
return sum;
}
int main() {
// Add your test cases here
std::cout << (solution(5, new int[5]{1, 2, 3, 4, 5}) == 9) << std::endl;
return 0;
}
代码梳理与分析
功能分析
-
目标:
- 给定一个整数数组,代表一组矩形柱的高度,找到其中能构成的最大矩形面积。
- 条件是矩形的宽度不能超过相邻柱子的范围(只能延展到高度大于等于当前柱子高度的位置)。
-
方法:
- 对每个柱子,依次检查向左和向右能够延展的最大宽度,然后计算以该柱子为高度的矩形面积。
- 遍历所有柱子,找出能够形成的最大矩形面积。
核心逻辑
代码分为三个部分:
-
遍历数组中的每个柱子
array[i],将其视为矩形的高度。 -
向左和向右扩展:
- 左侧扩展:找到左边第一个比当前柱子矮的位置,计算左侧能覆盖的宽度。
- 右侧扩展:找到右边第一个比当前柱子矮的位置,计算右侧能覆盖的宽度。
-
计算矩形面积,并更新最大值。
代码细节
-
初始化:
int currenth, currentw; int sum = 0;currenth表示当前柱子的高度。currentw表示以当前柱子为高度的矩形宽度。sum用来存储矩形面积的最大值。
-
遍历柱子:
for (int i = 0; i < n; i++) { currenth = array[i]; // 当前柱子的高度 currentw = 1; // 初始宽度为1(仅包含当前柱子) -
向左扩展:
for (int j = i - 1; j >= 0; j--) { if (array[j] >= currenth) { currentw += 1; // 左侧柱子满足条件,宽度增加 } else { break; // 左侧柱子不满足条件,停止扩展 } } -
向右扩展:
for (int j = i + 1; j < n; j++) { if (array[j] >= currenth) { currentw += 1; // 右侧柱子满足条件,宽度增加 } else { break; // 右侧柱子不满足条件,停止扩展 } } -
计算面积并更新最大值:
sum = max(sum, currenth * currentw); -
返回最大矩形面积:
return sum;
示例执行流程
测试用例
输入:
n = 5, array = {1, 2, 3, 4, 5};
流程分析:
-
i = 0(柱子高度为1):- 左侧无扩展(宽度=0)。
- 右侧扩展到索引4(宽度=4)。
- 面积:
1 * 5 = 5。
-
i = 1(柱子高度为2):- 左侧扩展到索引0(宽度=1)。
- 右侧扩展到索引4(宽度=3)。
- 面积:
2 * 4 = 8。
-
i = 2(柱子高度为3):- 左侧扩展到索引0(宽度=2)。
- 右侧扩展到索引4(宽度=2)。
- 面积:
3 * 3 = 9。
-
i = 3(柱子高度为4):- 左侧扩展到索引0(宽度=3)。
- 右侧扩展到索引4(宽度=1)。
- 面积:
4 * 3 = 8。
-
i = 4(柱子高度为5):- 左侧扩展到索引0(宽度=4)。
- 右侧无扩展(宽度=0)。
- 面积:
5 * 1 = 5。
最大矩形面积为 9。
代码性能分析
-
时间复杂度:
- 外层循环遍历所有柱子,时间复杂度为
O(n)。 - 每次循环中,左右各一次扩展操作,每次最多扩展到数组的两端,最坏情况为
O(n)。 - 总时间复杂度为
O(n^2),在数组较大时性能较差。
- 外层循环遍历所有柱子,时间复杂度为
-
空间复杂度:
- 仅使用几个变量存储临时值,空间复杂度为
O(1)。
- 仅使用几个变量存储临时值,空间复杂度为
优点与缺点
-
优点:
- 简单直观,容易理解。
- 不需要额外的数据结构。
-
缺点:
- 时间复杂度较高,无法应对较大的数据量。
- 每次扩展操作的范围是重复计算,效率低。
优化与改进
可以利用单调栈优化时间复杂度至 O(n) :
- 单调栈存储柱子的索引,维护一个递增序列。
- 在遇到当前柱子高度小于栈顶柱子时,计算栈顶柱子作为高度的最大矩形面积,并更新最大值。
- 通过栈的操作避免了重复遍历。
总结心得
-
思路清晰:当前代码是基于暴力解法,按照题目要求逐步构建解决方案。
-
可优化性:
- 使用单调栈优化扩展过程,可以显著提高效率。
-
算法设计原则:
- 应尽量减少重复计算,特别是在需要处理较大数据量时,时间复杂度的优化尤为重要。
-
学习体会:
- 通过这个题目可以熟悉矩形面积问题的基本解决思路,同时了解如何分析和优化算法。
经验总结:最大矩形面积问题
通过实现和分析这道算法题,以下是一些个人总结的经验:
1. 解决问题需要清晰的思路
-
在面对复杂问题时,将问题拆分为可管理的子任务是关键。
- 本题中,矩形面积 = 高度 × 宽度。明确宽度和高度的确定条件后,问题的解决变得更加直观。
-
暴力解法提供了一个基础解决方案,是理解问题的起点。
-
在暴力解法的基础上,可以发现问题的低效部分,例如重复计算,从而寻找优化的方向。
2. 算法优化的本质:减少冗余计算
-
优化算法的目标是减少不必要的重复操作。
- 本题中,暴力解法需要从当前柱子分别向左右扩展,耗费
O(n^2)时间。 - 单调栈优化则通过预先维护索引信息,避免重复扩展,直接将时间复杂度降低到
O(n)。
- 本题中,暴力解法需要从当前柱子分别向左右扩展,耗费
3. 数据结构的选择是算法优化的关键
-
单调栈是本题优化的核心:
- 栈中存储的索引不仅有序,还包含了隐式的范围信息,省去了逐个扩展的过程。
- 利用栈的“后进先出”特性,可以快速计算矩形的宽度。
-
合适的数据结构往往能够有效提升算法性能。例如:
- 单调栈适用于需要动态维护顺序的场景。
- 哈希表适合快速查找。
- 优先队列则适合处理动态最大或最小值的问题。
4. 测试案例设计的重要性
-
充分的测试可以验证算法的正确性和鲁棒性。测试用例应包含:
- 基本情况:如数组中只有一个柱子。
- 特殊情况:如所有柱子高度相等,或高度为零。
- 边界情况:如数组为空或只有一个元素。
-
本题中的测试用例:
cpp 复制代码 solution({1, 2, 3, 4, 5}); // 单调递增 solution({5, 4, 3, 4, 5}); // 中间最低 solution({2, 1, 5, 6, 2, 3}); // 混合数据
5. 暴力解法和优化解法并存的价值
-
暴力解法:
- 虽然效率低,但实现简单,适合快速验证算法是否正确。
- 对于小规模问题,这种方法的时间消耗是可以接受的。
-
优化解法:
- 提升性能,适合大规模数据的处理。
- 通过引入额外的数据结构(如栈),可以充分发挥时间复杂度上的优势。