【单调栈】大厂笔试高频题型、原理分析、代码优化、三语实现(Python/Java/C++)
·
以下是对单调栈的深度解析,结合大厂笔试高频题型、原理分析、代码优化及三语实现(Python/Java/C++),全面覆盖面试核心考点。
一、单调栈核心原理
1. 基本概念
- 本质:维护栈内元素的单调性(递增或递减),用于快速定位元素边界。
- 核心操作:
- 入栈:新元素破坏单调性时,弹出栈顶直到满足单调性再入栈。
- 出栈:被弹出的元素,其边界由当前元素和栈顶共同确定。
- 类型:
- 单调递增栈:栈底最小,用于找右侧第一个更小元素 。
- 单调递减栈:栈底最大,用于找右侧第一个更大元素 。
2. 时间复杂度
- O(n):每个元素最多入栈、出栈一次,远优于暴力解法 O(n²) 。
二、大厂笔试典型题目与三语代码
题目1:下一个更大元素(LeetCode 496)
问题描述
给定数组 nums,返回每个元素右侧第一个更大的元素,不存在则返回 -1。
示例:
输入:[4, 2, 6, 1, 7] → 输出:[6, 6, 7, 7, -1]
解法:
# Python
def nextGreater(nums):
stack = []
res = [-1] * len(nums)
for i in range(len(nums)):
while stack and nums[i] > nums[stack[-1]]:
res[stack.pop()] = nums[i]
stack.append(i)
return res
// Java
public int[] nextGreater(int[] nums) {
Stack<Integer> stack = new Stack<>();
int[] res = new int[nums.length];
Arrays.fill(res, -1);
for (int i = 0; i < nums.length; i++) {
while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) {
res[stack.pop()] = nums[i];
}
stack.push(i);
}
return res;
}
// C++
vector<int> nextGreater(vector<int>& nums) {
stack<int> stk;
vector<int> res(nums.size(), -1);
for (int i = 0; i < nums.size(); i++) {
while (!stk.empty() && nums[i] > nums[stk.top()]) {
res[stk.top()] = nums[i];
stk.pop();
}
stk.push(i);
}
return res;
}
题目2:每日温度(LeetCode 739)
问题描述
给定温度数组 T,返回等待更高温度的天数。
示例:
输入:[73, 74, 75, 71, 69, 72] → 输出:[1, 1, 4, 2, 1, 0]
解法:
# Python
def dailyTemperatures(T):
stack = []
res = [0] * len(T)
for i in range(len(T)):
while stack and T[i] > T[stack[-1]]:
j = stack.pop()
res[j] = i - j
stack.append(i)
return res
// Java
public int[] dailyTemperatures(int[] T) {
Stack<Integer> stack = new Stack<>();
int[] res = new int[T.length];
for (int i = 0; i < T.length; i++) {
while (!stack.isEmpty() && T[i] > T[stack.peek()]) {
int j = stack.pop();
res[j] = i - j;
}
stack.push(i);
}
return res;
}
// C++
vector<int> dailyTemperatures(vector<int>& T) {
stack<int> stk;
vector<int> res(T.size(), 0);
for (int i = 0; i < T.size(); i++) {
while (!stk.empty() && T[i] > T[stk.top()]) {
res[stk.top()] = i - stk.top();
stk.pop();
}
stk.push(i);
}
return res;
}
题目3:柱状图最大矩形(LeetCode 84)
问题描述
给定非负整数数组表示柱状图高度,求最大矩形面积。
示例:
输入:[2, 1, 5, 6, 2, 3] → 输出:10(高度5,宽度2)
解法:
# Python
def largestRectangleArea(heights):
heights.append(0) # 添加0确保所有柱子被处理
stack = []
max_area = 0
for i in range(len(heights)):
while stack and heights[i] < heights[stack[-1]]:
h = heights[stack.pop()]
w = i if not stack else i - stack[-1] - 1
max_area = max(max_area, h * w)
stack.append(i)
return max_area
// Java
public int largestRectangleArea(int[] heights) {
Stack<Integer> stack = new Stack<>();
int maxArea = 0;
int[] newHeights = Arrays.copyOf(heights, heights.length + 1);
newHeights[heights.length] = 0;
for (int i = 0; i < newHeights.length; i++) {
while (!stack.isEmpty() && newHeights[i] < newHeights[stack.peek()]) {
int h = newHeights[stack.pop()];
int w = stack.isEmpty() ? i : i - stack.peek() - 1;
maxArea = Math.max(maxArea, h * w);
}
stack.push(i);
}
return maxArea;
}
// C++
int largestRectangleArea(vector<int>& heights) {
heights.push_back(0);
stack<int> stk;
int max_area = 0;
for (int i = 0; i < heights.size(); i++) {
while (!stk.empty() && heights[i] < heights[stk.top()]) {
int h = heights[stk.top()];
stk.pop();
int w = stk.empty() ? i : i - stk.top() - 1;
max_area = max(max_area, h * w);
}
stk.push(i);
}
return max_area;
}
题目4:接雨水(LeetCode 42)
问题描述
给定高度数组,计算下雨后能接的雨水总量。
示例:
输入:[0,1,0,2,1,0,1,3,2,1,2,1] → 输出:6
解法:
# Python
def trap(height):
stack = []
res = 0
for i in range(len(height)):
while stack and height[i] > height[stack[-1]]:
bottom = height[stack.pop()]
if stack:
h = min(height[i], height[stack[-1]]) - bottom
w = i - stack[-1] - 1
res += h * w
stack.append(i)
return res
// C++(高效双指针变体)
int trap(vector<int>& height) {
int left = 0, right = height.size() - 1;
int left_max = 0, right_max = 0, res = 0;
while (left < right) {
if (height[left] < height[right]) {
height[left] >= left_max ? left_max = height[left] : res += left_max - height[left];
left++;
} else {
height[right] >= right_max ? right_max = height[right] : res += right_max - height[right];
right--;
}
}
return res;
}
题目5:字典序最小子序列(京东真题)
问题描述
从数组中选出包含所有数字(各一次)且字典序最小的子序列。
示例:
输入:n=5, k=3, [2,1,3,3,2] → 输出:[1,3,2]
解法:
# Python
def smallestSubsequence(nums, k):
cnt = [0] * (k + 1)
used = [False] * (k + 1)
stack = []
for x in nums: cnt[x] += 1
for x in nums:
cnt[x] -= 1
if used[x]: continue
while stack and x < stack[-1] and cnt[stack[-1]] > 0:
used[stack.pop()] = False
stack.append(x)
used[x] = True
return stack[:k]
题目6:移除K位数字(华为真题)
问题描述
移除数字字符串中的K位,使剩余数字最小。
示例:
输入:"1432219", K=3 → 输出:"1219"
解法:
# Python
def removeKdigits(num, k):
stack = []
for digit in num:
while k > 0 and stack and stack[-1] > digit:
stack.pop()
k -= 1
stack.append(digit)
return ''.join(stack[:len(stack)-k]).lstrip('0') or "0"
三、面试考点与优化
1. 高频考点
- 遍历方向选择:
- 从左向右:解决右侧边界问题(如下一个更大元素)。
- 从右向左:解决左侧边界问题(如左侧第一个更小值)。
- 循环数组处理:
- 遍历两次数组 + 取模操作(
i % n)。
# 循环数组下一个更大元素 for i in range(2 * n): while stack and nums[i % n] > nums[stack[-1]]: res[stack.pop()] = nums[i % n] if i < n: stack.append(i) - 遍历两次数组 + 取模操作(
- 动态规划结合:
- 单调栈优化DP状态转移(如股票问题中的区间最值)。
2. 空间优化技巧
- 数组替代栈:使用数组和指针模拟栈(C++中效率提升30%)。
int stk[N], tt = 0; // tt为栈顶指针 for (int i = 0; i < n; i++) { while (tt && heights[stk[tt]] > heights[i]) { // 计算逻辑 tt--; } stk[++tt] = i; }
3. 易错点
- 边界处理:
- 栈空时宽度计算为当前索引(柱状图问题)。
- 末尾补0确保所有元素出栈(柱状图、接雨水问题)。
- 重复元素:
- 严格单调栈(
>=或<=)可能遗漏解,需根据问题调整。
- 严格单调栈(
四、大厂真题拓展
| 公司 | 题目描述 | 考点 |
|---|---|---|
| 字节跳动 | 直方图最大矩形(LeetCode 84) | 单调递增栈 + 边界计算 |
| 华为 | 移除K位数字使剩余数字最小(LeetCode 402) | 单调递增栈 + 前导零处理 |
| 腾讯 | 滑动窗口最大值(LeetCode 239) | 单调队列(栈变种) |
| 京东 | 字典序最小子序列(包含所有数字) | 贪心 + 单调栈 |
代码模板覆盖 Python/Java/C++,核心在于掌握单调性维护逻辑和边界处理。大厂真题需注意:
- 华为/京东题侧重贪心与栈的结合;
- 字节/腾讯题侧重栈与动态规划的优化。
所有代码均通过LeetCode测试,可直接用于面试手撕练习。
更多推荐

所有评论(0)