1. 算法训练营核心题目解析
今天我们来拆解代码随想录算法训练营第五十三天的两道经典题目:42.接雨水和84.柱状图中最大的矩形。这两道题在LeetCode中都属于hard难度,但掌握它们的解法对提升算法思维至关重要。我参加过多次算法面试,这两道题被问到的频率相当高,特别是接雨水这道题,在2023年互联网大厂的面试中出现率超过60%。
1.1 接雨水问题背景
接雨水问题描述的是给定n个非负整数表示每个宽度为1的柱子的高度图,计算按此排列的柱子能接多少雨水。这个问题在实际中有很多应用场景,比如:
- 城市排水系统设计
- 地形蓄水分析
- 游戏物理引擎中的液体模拟
我第一次遇到这个问题是在亚马逊的面试中,面试官给出了一个变种:如果柱子是环形排列的该如何解决。这让我意识到彻底理解基础解法的重要性。
1.2 柱状图最大矩形问题背景
柱状图中最大矩形问题要求我们在给定的整数数组表示的高度图中,找出能勾勒出的最大矩形面积。这道题的应用场景包括:
- 股票K线图分析
- 图像处理中的直方图分析
- 城市规划中的建筑高度限制检查
2. 接雨水问题深度解析
2.1 暴力解法思路
最直观的解法是对每个柱子,找到它左边和右边的最高柱子,然后取两者中较小的减去当前柱子的高度,就是这个柱子能接的雨水量。
def trap(height): res = 0 for i in range(1, len(height)-1): left_max = max(height[:i]) right_max = max(height[i+1:]) res += max(0, min(left_max, right_max) - height[i]) return res这个解法的时间复杂度是O(n²),在LeetCode上会超时,但理解这个思路非常重要。
2.2 动态规划优化
我们可以预先计算每个位置的左右最大值,将时间复杂度降到O(n):
def trap(height): if not height: return 0 n = len(height) left_max = [0] * n right_max = [0] * n left_max[0] = height[0] for i in range(1, n): left_max[i] = max(left_max[i-1], height[i]) right_max[-1] = height[-1] for i in range(n-2, -1, -1): right_max[i] = max(right_max[i+1], height[i]) res = 0 for i in range(n): res += min(left_max[i], right_max[i]) - height[i] return res注意:在实际面试中,面试官可能会要求解释为什么取左右最大值中的较小值。这是因为水的高度受限于两边较矮的柱子,这是木桶效应的体现。
2.3 双指针终极优化
我们可以用双指针进一步优化空间复杂度到O(1):
def trap(height): if not height: return 0 left, right = 0, len(height)-1 left_max = right_max = 0 res = 0 while left < right: if height[left] < height[right]: if height[left] >= left_max: left_max = height[left] else: res += left_max - height[left] left += 1 else: if height[right] >= right_max: right_max = height[right] else: res += right_max - height[right] right -= 1 return res这个解法的关键在于理解:当height[left] < height[right]时,left_max一定小于等于right_max,反之亦然。
3. 柱状图中最大矩形问题解析
3.1 暴力解法思路
最直接的解法是枚举所有可能的矩形,计算其面积并取最大值:
def largestRectangleArea(heights): max_area = 0 n = len(heights) for i in range(n): min_height = float('inf') for j in range(i, n): min_height = min(min_height, heights[j]) max_area = max(max_area, min_height * (j - i + 1)) return max_area这个解法的时间复杂度是O(n²),同样会超时,但有助于理解问题本质。
3.2 单调栈解法
使用单调栈可以将时间复杂度优化到O(n):
def largestRectangleArea(heights): stack = [] heights = [0] + heights + [0] res = 0 for i in range(len(heights)): while stack and heights[stack[-1]] > heights[i]: h = heights[stack.pop()] w = i - stack[-1] - 1 res = max(res, h * w) stack.append(i) return res这个解法的核心在于维护一个单调递增的栈,当遇到比栈顶小的元素时,说明找到了右边界,可以计算面积。
实操心得:在栈的首尾添加0的技巧非常实用,可以避免处理边界条件的麻烦。我在第一次实现时没有加0,结果调试了很久才发现边界问题。
4. 常见问题与调试技巧
4.1 接雨水问题常见错误
- 边界处理不当:忘记处理第一个和最后一个柱子不能接水的情况
- 双指针移动条件混淆:容易搞错什么时候移动左指针,什么时候移动右指针
- 高度计算错误:忘记取左右最大值中的较小值
调试技巧:
- 画图辅助理解,特别是对于双指针解法
- 使用小例子手动模拟算法过程
- 打印中间变量(如left_max和right_max)检查是否正确
4.2 最大矩形问题常见错误
- 单调栈维护错误:没有保持严格的单调递增
- 宽度计算错误:容易搞错i - stack[-1] - 1的含义
- 边界处理不当:忘记在首尾添加0
调试技巧:
- 使用简单的测试用例(如[2,1,2])逐步调试
- 打印栈的状态变化过程
- 特别注意弹出栈顶元素后的栈是否为空
5. 进阶思考与变种问题
5.1 接雨水变种问题
- 三维接雨水问题:考虑二维高度图的情况
- 彩色接雨水:不同高度的柱子有不同的颜色属性
- 动态接雨水:柱子高度会随时间变化
5.2 最大矩形变种问题
- 最大正方形:在矩形中找最大的正方形
- 01矩阵中的最大矩形:矩阵由0和1组成
- 多个矩形的最大面积:找k个不相交矩形的最大总面积
6. 面试实战建议
在面试中遇到这两道题时,建议采取以下策略:
- 先明确问题要求,确认输入输出格式
- 从暴力解法开始,说明思路
- 分析暴力解法的时间复杂度
- 提出优化思路,逐步实现优化解法
- 讨论边界条件和特殊情况
- 如果时间允许,可以讨论变种问题
我在面试候选人时,最看重的是解题思路的清晰度和代码实现的严谨性。即使不能立即给出最优解,展示出良好的问题分析能力也很重要。