以下是对单调栈的深度解析,结合大厂笔试高频题型、原理分析、代码优化及三语实现(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. 高频考点
  1. 遍历方向选择:
    • 从左向右:解决右侧边界问题(如下一个更大元素)。
    • 从右向左:解决左侧边界问题(如左侧第一个更小值)。
  2. 循环数组处理:
    • 遍历两次数组 + 取模操作(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)
    
  3. 动态规划结合:
    • 单调栈优化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测试,可直接用于面试手撕练习。

更多推荐