直方图最大矩形面积:为何需用栈?我的解法是否有效?
首先直接给出结论:你的当前解法是无效的——运行你提供的测试用例[4, 2, 1, 8, 6, 8, 5, 2],你的代码返回12,和预期的20不符。接下来我们拆解问题,逐一解答你的疑问:
一、为什么通常用栈解决这个问题?
我们的核心思路确实是计算以每个柱子为最矮柱的最大矩形面积,这需要为每个柱子找到:
left_bound[i]:左侧第一个高度小于H[i]的柱子索引(不存在则为-1)right_bound[i]:右侧第一个高度小于H[i]的柱子索引(不存在则为数组长度n)
此时以H[i]为高度的矩形宽度是right_bound[i] - left_bound[i] - 1,面积为H[i] * (right_bound[i] - left_bound[i] - 1)。
栈的价值在于它能以O(n)线性时间完成所有边界的计算:
栈会维护一个单调递增的索引序列,保证栈顶对应的柱子高度是栈中最小的。当遇到一个高度小于栈顶的柱子时,我们就能确定栈顶柱子的右边界是当前索引,左边界是栈顶的前一个元素(或-1),可以直接计算该柱子的面积,同时弹出栈顶,直到栈重新保持单调递增。
这种方法每个柱子入栈、出栈各一次,时间效率极高,能轻松处理大规模数据(比如n=10^5的场景),而暴力遍历找边界的方法是O(n²),数据量大时会直接超时。
二、你的解法存在的核心问题
1. 边界数组(left/right)的计算逻辑错误
你定义的right[i]是“右侧首个大于H[i]的柱”,这本身就和核心思路相悖——我们需要的是首个小于H[i]的柱来确定边界。而且你的计算逻辑:
right[i] = i + 1 if lst[i] > lst[i + 1] else right[i + 1]
只能处理连续递减的简单场景,遇到中间有更大元素的情况就会出错。比如测试用例中索引2的柱子值为1,右侧所有元素都比它大,正确的右边界应该是8(数组长度),但你的代码会把right[2]设为right[3]=4,完全错误。
同样,left数组的计算逻辑也无法正确处理所有情况,比如数组[2,3,1]中,索引2的柱子值为1,左侧所有元素都比它大,正确左边界是-1,但你的代码会把left[2]设为left[1]=0,明显错误。
2. 面积计算逻辑完全偏离核心思路
你代码中计算面积的部分:
right_len = right[i] - i -1 left_len = i - left[i] + 1 h = min(lst[right_len - 1], lst[left_len + 1]) res = (right_len + left_len) * h
这段逻辑完全脱离了“以当前柱为最矮柱”的核心,right_len、left_len的计算没有意义,h取的是两个无关位置的最小值,导致计算出的面积和实际情况毫无关联。比如测试用例中索引5的柱子值为8,实际面积是8*1=8,但你的代码计算出的结果是4,完全错误。
三、不用栈的有效解法参考
如果想不用栈,暴力法是最直接的有效实现(虽然时间复杂度高),它通过对每个柱子向左、向右遍历找到正确边界:
def max_area_rect(lst): n = len(lst) max_area = 0 for i in range(n): # 找左侧第一个小于当前高度的索引 left = i while left > 0 and lst[left-1] >= lst[i]: left -= 1 # 找右侧第一个小于当前高度的索引 right = i while right < n-1 and lst[right+1] >= lst[i]: right += 1 # 计算面积并更新最大值 area = lst[i] * (right - left + 1) max_area = max(max_area, area) return max_area
这个实现能得到正确结果,但对于n>10^4的数组会超时,这时候栈解法的优势就体现出来了。
内容的提问来源于stack exchange,提问作者socksocket

