目录
[一、柱状图中最大的矩形(LeetCode 84・困难)](#一、柱状图中最大的矩形(LeetCode 84・困难))
[Java 代码实现(标准单调栈版)](#Java 代码实现(标准单调栈版))
[二、数组中的第 K 个最大元素(LeetCode 215・中等)](#二、数组中的第 K 个最大元素(LeetCode 215・中等))
[Java 代码实现(小顶堆版)](#Java 代码实现(小顶堆版))
今天我们来拆解两道算法面试高频题:柱状图中最大的矩形 (单调栈难题)和数组中的第 K 个最大元素 (堆的经典应用)。这两道题分别代表了单调栈 和堆两大核心数据结构的标杆应用,吃透它们能帮你彻底掌握这两种结构的灵活使用,是算法面试的必考点。
一、柱状图中最大的矩形(LeetCode 84・困难)
题目描述
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例:
plaintext
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10
输入:heights = [2,4]
输出:4
解题思路
这道题的核心是找到每个柱子能扩展的最大左右边界 ,从而计算以该柱子为高的最大矩形面积。暴力法时间复杂度为 O(n2),而单调栈可以将时间复杂度优化到 O(n)。
核心思想
- 维护一个单调递增栈:栈中存储柱子的下标,保证栈中下标对应的高度始终递增。
- 遍历数组,找左右边界 :
- 对于当前柱子
i,若栈顶柱子高度heights[stack.peek()] >= heights[i],说明栈顶柱子的右边界 就是i; - 弹出栈顶下标
top,计算以heights[top]为高的最大矩形面积:- 左边界为新的栈顶下标
left = stack.peek()(若栈空则为-1) - 宽度为
i - left - 1 - 面积为
heights[top] * 宽度
- 左边界为新的栈顶下标
- 若栈顶高度小于当前高度,将
i压入栈中。
- 对于当前柱子
- 处理栈中剩余元素 :遍历结束后,栈中剩余柱子的右边界为数组长度
n,重复上述计算过程。 - 最终返回最大面积。
Java 代码实现(标准单调栈版)
java
运行
import java.util.Stack;
public class LargestRectangleInHistogram {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int maxArea = 0;
// 栈中存储柱子下标,维护单调递增
Stack<Integer> stack = new Stack<>();
for (int i = 0; i <= n; i++) {
// 用0作为哨兵,处理栈中剩余元素
int currentHeight = (i == n) ? 0 : heights[i];
// 当前高度小于栈顶高度,弹出并计算面积
while (!stack.isEmpty() && heights[stack.peek()] >= currentHeight) {
int top = stack.pop();
int height = heights[top];
// 左边界:栈空则为-1,否则为栈顶下标
int left = stack.isEmpty() ? -1 : stack.peek();
// 宽度 = 右边界(i) - 左边界(left) - 1
int width = i - left - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
return maxArea;
}
}
复杂度分析
- 时间复杂度:O(n),每个下标入栈 / 出栈最多一次,总操作次数为 O(n)
- 空间复杂度:O(n),最坏情况(严格递增)栈的大小为 O(n)
核心知识点总结
- 单调递增栈的作用:快速找到每个柱子的左右边界,避免暴力遍历
- 哨兵优化 :在数组末尾补
0,统一处理栈中剩余元素,简化代码逻辑 - 边界计算 :左边界为栈顶下标(栈空则为
-1),右边界为当前下标,宽度为右-左-1
二、数组中的第 K 个最大元素(LeetCode 215・中等)
题目描述
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例:
plaintext
输入: [3,2,1,5,6,4], k = 2
输出: 5
输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4
解题思路
这道题有两种经典解法:小顶堆 (时间复杂度 O(nlogk))和快速选择 (时间复杂度 O(n),平均情况),这里重点讲解面试最常用的小顶堆解法。
核心思想(小顶堆)
- 维护一个大小为
k的小顶堆 :堆顶始终是当前堆中的最小值(即前k大元素中的最小值)。 - 遍历数组 :
- 若堆大小小于
k,直接将元素入堆; - 若堆大小等于
k,比较当前元素与堆顶:- 若当前元素大于堆顶,弹出堆顶,将当前元素入堆;
- 否则跳过。
- 若堆大小小于
- 遍历结束后,堆顶元素即为第
k大元素。
Java 代码实现(小顶堆版)
java
运行
import java.util.PriorityQueue;
public class KthLargestElementInArray {
public int findKthLargest(int[] nums, int k) {
// 小顶堆,默认自然排序
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int num : nums) {
if (minHeap.size() < k) {
minHeap.offer(num);
} else if (num > minHeap.peek()) {
minHeap.poll();
minHeap.offer(num);
}
}
return minHeap.peek();
}
}
复杂度分析
- 时间复杂度:O(nlogk),每个元素入堆 / 出堆的时间复杂度为 O(logk),遍历数组时间为 O(n)
- 空间复杂度 :O(k),堆的大小为
k
核心知识点总结
- 小顶堆的优势:空间复杂度低(仅需 O(k)),适合处理海量数据(如流式数据)
- 堆的选择 :求第
k大用小顶堆,求第k小用大顶堆 - 快速选择拓展:基于快速排序的分治思想,平均时间复杂度 O(n),最坏 O(n2),适合面试加分
三、两道题的核心对比与联系
表格
| 题目 | 核心目标 | 数据结构 | 核心逻辑 |
|---|---|---|---|
| 柱状图中最大的矩形 | 找每个柱子的最大左右边界,计算最大面积 | 单调递增栈 | 维护栈的单调性,快速定位边界,优化时间复杂度 |
| 数组中的第 K 个最大元素 | 找数组中第 K 大的元素 | 小顶堆 | 维护大小为 k 的小顶堆,堆顶即为第 K 大元素 |
核心联系
两道题分别代表了单调栈 和堆两大核心数据结构的经典应用:
- 单调栈:解决「找左右边界、下一个更大 / 更小元素」等序列问题,时间复杂度 O(n)
- 堆:解决「TopK、优先级队列」等问题,时间复杂度 O(nlogk),空间复杂度低