力扣经典算法:基于容器适配器的栈/队列动态规划解法
·
基于容器适配器的栈/队列在动态规划中的应用:以柱状图中最大矩形为例
在算法问题中,容器适配器(如栈和队列)常用于优化动态规划(DP)解法,通过高效存储中间状态来降低时间复杂度。LeetCode经典题目“84. 柱状图中最大矩形”就是一个典型例子,它结合单调栈和动态规划思想,实现$O(n)$时间复杂度的求解。下面我将逐步解释其原理和实现。
问题描述
给定一个整数数组$heights$,表示柱状图的高度(每个柱子的宽度为1),求图中最大矩形的面积。例如,输入$heights = [2,1,5,6,2,3]$,输出最大面积为10(对应高度为5和6的柱子形成的矩形)。
动态规划思路
核心是预处理每个柱子$i$的左右边界:
- 左边界 $left[i]$:柱子$i$左边第一个高度小于$height[i]$的柱子索引。
- 右边界 $right[i]$:柱子$i$右边第一个高度小于$height[i]$的柱子索引。
- 则矩形面积$A_i$可计算为: $$A_i = height[i] \times (right[i] - left[i] - 1)$$
- 最终最大面积为$\max_{i} A_i$。
直接计算所有$left[i]$和$right[i]$需要$O(n^2)$时间,但使用单调栈(一种基于栈的容器适配器)可以优化到$O(n)$。
使用单调栈优化动态规划
单调栈维护一个递增序列的索引,确保栈顶元素对应的高度最小。遍历数组时,栈用于快速找到边界:
-
预处理左边界:
- 初始化栈$stack$和数组$left$(全为-1)。
- 从左向右遍历$heights$:
- 如果栈非空且当前高度$height[i] \leq height[stack[-1]]$,则弹出栈顶(保持单调性)。
- $left[i] = stack[-1]$ if stack not empty, else -1。
- 将$i$压入栈。
- 这确保了$left[i]$是$i$左边第一个较小高度的索引。
-
预处理右边界:
- 类似地,初始化栈$stack$和数组$right$(全为$n$,$n$是数组长度)。
- 从右向左遍历$heights$:
- 如果栈非空且$height[i] \leq height[stack[-1]]$,则弹出栈顶。
- $right[i] = stack[-1]$ if stack not empty, else $n$。
- 将$i$压入栈。
- 这确保了$right[i]$是$i$右边第一个较小高度的索引。
-
计算最大面积:
- 遍历每个柱子$i$,计算面积$A_i = height[i] \times (right[i] - left[i] - 1)$。
- 取所有$A_i$的最大值。
时间复杂度为$O(n)$,空间复杂度$O(n)$,得益于栈的高效操作。
代码实现
以下是Python实现,使用列表模拟栈(容器适配器):
def largestRectangleArea(heights):
n = len(heights)
left = [-1] * n # 左边界数组
right = [n] * n # 右边界数组
stack = [] # 用列表作为栈适配器
# 预处理左边界
for i in range(n):
while stack and heights[i] <= heights[stack[-1]]:
stack.pop()
left[i] = stack[-1] if stack else -1
stack.append(i)
stack = [] # 清空栈,用于右边界
# 预处理右边界
for i in range(n-1, -1, -1):
while stack and heights[i] <= heights[stack[-1]]:
stack.pop()
right[i] = stack[-1] if stack else n
stack.append(i)
# 计算最大面积
max_area = 0
for i in range(n):
width = right[i] - left[i] - 1
area = heights[i] * width
if area > max_area:
max_area = area
return max_area
总结
- 优势:单调栈作为容器适配器,简化了动态规划中的状态转移,将边界查找从$O(n^2)$优化到$O(n)$。
- 适用场景:类似问题如“滑动窗口最大值”(使用队列适配器)或“接雨水”,都可通过栈/队列优化DP。
- 关键点:确保栈/队列的单调性,以高效处理递增或递减序列。在实际编码中,注意边界条件(如数组为空时)。
更多推荐
所有评论(0)