问题描述
格式要求
数据范围
1 ) 算法1:时间复杂度为: O ( n 3 ) O(n^3) O(n3)的实现
分析
/*
* n:表示n列直方图
* h:表示高度的数组,h[i]表示第i列的高度(下标从1开始)
*/
int getResult(int n, int *h) {
// 最后答案
int res = 0;
// 第一层循环枚举直方图左边界位置
for (int a = 1; a <= n; ++a) {
// 第二层循环枚举直方图右边界位置
for (int b = a; b <= n; ++b) {
// 要求,保证所有 h[i] 不超过 32767,记录最小高度, 这里只做数据的初始化,取一个比最大值大的值即可
int minH = 50000;
// 第三层循环从a到b更新最小高度,这里的高要找最矮的,木桶效应
for (int c = a; c <= b; ++c) {
minH = min(minH, h[c]); // 更新最小高度
}
res = max(res, (b - a + 1) * minH); // 更新最大面积
}
}
return res;
}
总结:太慢了,属于比较原始的蛮力算法,没有任何优化的算法, 这是能够直接想到的
2 ) 算法2:减少循环, 时间复杂度为: O ( n 2 ) O(n^2) O(n2)的实现优化版本
分析
int getResult(int n, int *h) {
// 最后答案
int res = 0;
// 第一层循环枚举直方图左边界位置
for (int a = 1; a <= n; ++a) {
// 要求,保证所有 h[i] 不超过 32767,记录最小高度, 这里只做数据的初始化,取一个比最大值大的值即可
int minH = 50000;
// 第二层循环枚举直方图右边界位置
// 思路:a到b的最小高度H1 和 a到b+1的最小高度H2的关系:H2=min(H1, h_{b+1})
// 由此减少了一次循环
for (int b = a; b <= n; ++b) {
minH = min(minH, h[b]); // 更新最小高度
res = max(res, (b - a + 1) * minH); // 更新最大面积
}
}
return res;
}
总结:优化思路是在a和b的底之间找到最小高度
3 ) 算法3:通过卡位来计算,时间复杂度为: O ( n 2 ) O(n^2) O(n2)的实现优化版本
// 卡位计算, 蛮力算法
int getResult(int n, int *h) {
// 最后答案
int res = 0;
// 第一层循环枚举直方图所有的列, 从1~n, 表示第1列到第n列
for (int a = 1; a <= n; ++a) {
// 取出当前列的高度
int curH = h[a];
// 左卡位点
int lo = 0;
// 右卡位点
int hi = 0;
// 第二层找到当前列的左右卡位点,并计算最大矩形面积
// 1.找到左卡位点
for (lo = a - 1; lo >= 1; lo --){
if(h[lo] < curH) {
break;
}
}
// 2.找到右卡位点
for (hi = a + 1; lo <= n; hi++){
if(h[hi] < curH) {
break;
}
}
// 3.计算当前最大面积
res = max(res, (hi - lo - 1) * curH);
}
return res;
}
4 ) 算法4:通过单调栈来计算,时间复杂度为: O ( n ) O(n) O(n)的实现优化版本
// 单调栈方式实现
int getResult(int n, int *h) {
// 最后答案
int res = 0;
// 单调栈,记录的是在h数组中的位置,栈顶所对应的高度是最高的,也就是单调递增
stack<int> myStack;
// 初始化单调栈,提前压入一个哨兵,总体有前后两个哨兵,分别是h[0]和h[n+1],高度都是0
myStack.push(0);
// 进行栈过滤, 从1到n+1, 最后一个也是一个哨兵0, 可以循环n+1次
for (int i = 1; i <= n + 1; ++i) {
// 如果栈顶元素高度大于当前元素的高度时,则弹出栈顶
while(h[myStack.top()] > h[i]) {
// 记录栈顶元素高度
int nowHeight = h[myStack.top()];
// 弹出栈顶
myStack.pop();
// 比较最大面积
res = max(res, (i - myStack.top() - 1) * nowHeight);
}
// 将当前下标插入栈中
myStack.push(i);
}
return res;
}
如果不考虑while, 程序变形:
// 单调栈方式实现 形式转换
int getResult(int n, int *h) {
// 最后答案
int res = 0;
// 单调栈,记录的是在h数组中的位置,栈顶所对应的高度是最高的,也就是单调递增
stack<int> myStack;
// 初始化单调栈,提前压入一个哨兵,总体有前后两个哨兵,分别是h[0]和h[n+1],高度都是0
myStack.push(0);
// 进行栈过滤, 从1到n+1, 最后一个也是一个哨兵0, 可以循环n+1次
for (int i = 0; i <= n+1;) {
if(myStack.empty() || h[myStack.top()] <= h[i]) {
// 进栈
myStack.push(i);
i++;
} else {
// 记录当前栈顶元素高度
int nowHeight = h[myStack.top()];
// 弹出栈顶
myStack.pop();
// 比较最大面积
res = max(res, (i - myStack.top() - 1) * nowHeight);
}
}
return res;
}
总结:对于每一列, 找到左右两端的卡位hi和lo, 形成的矩形面积为:底×高, 即:(hi - lo - 1) * h, 可见这是一个线性的算法O(n)
算法5:通过分治策略来处理
分析
int main() {
int n;
cin >> n;
int *height = new int[n + 2]();
// 在这里控制输入中的height[0]和height[n+1]均为0
for (int i = 1; i <= n; ++i) {
cin >> height[i];
}
cout << getResult(n, height) << endl;
delete[] height;
return 0;
}