单调栈解左侧更小元素:用栈存下标维护递增栈,遍历时弹出≥当前元素的栈顶,剩余栈顶即左侧第一个更小值;不可用set因丢失位置信息,“最近”需下标最大而非值最小。

用单调栈解决左侧更小元素问题
直接用 std::stack 维护一个递增栈,遍历数组时,栈顶比当前元素 ≥ 的都弹出,剩下栈顶就是左侧第一个更小元素。这是最常用且时间复杂度为 O(n) 的解法,避免了暴力 O(n²) 的嵌套循环。
关键点在于:栈里存的是**下标**(不是值),这样既能比较值大小,又能准确填入结果数组对应位置。
- 初始化结果数组
res全为 -1(表示无左侧更小元素) - 遍历
i从 0 到n-1,对每个nums[i],while 栈非空且nums[stk.top()] >= nums[i]就stk.pop() - 若栈非空,
res[i] = nums[stk.top()];然后把i压栈
为什么不能用 set 或 map 替代栈
std::set 虽然能查前驱,但无法保证“最近”——它只按值排序,丢失了位置信息;而左侧更小元素必须是**位置在左、且下标最大**的那个更小值。单调栈天然保留了从左到右的顺序和最近性。
例如 nums = [5, 3, 4, 2],处理到 i=2(值为 4)时,栈中应剩 [1](即下标 1,值 3),这时 res[2] = 3;若用 set 插入所有左侧元素再找 lower_bound,会得到 2(下标 3),但它在右侧,完全错误。
立即学习“C++免费学习笔记(深入)”;
边界与易错点:空栈、相等情况、重复值
常见错误是写成 nums[stk.top()] > nums[i],漏掉等于情况。题目要求“更小”,所以等于必须弹出——否则相同值会挡住真正更小的候选。
- 栈为空时,
res[i]保持 -1,不用额外判断 - 输入含重复值(如
[2,2,2])时,每个元素的左侧更小元素都是 -1,栈会逐个清空再压入新下标 - 不要在循环内反复调用
stk.empty()多次,先存变量或合并进 while 条件
C++ 完整可跑示例(含注释)
#include <vector>
#include <stack>
std::vector<int> leftSmaller(const std::vector<int>& nums) {
int n = nums.size();
std::vector<int> res(n, -1);
std::stack<int> stk; // 存下标
for (int i = 0; i < n; ++i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
res[i] = nums[stk.top()];
}
stk.push(i);
}
return res;
}
注意 nums[stk.top()] >= nums[i] 中的等号不能省;返回的是值而非下标,符合多数使用场景。如果需要下标,把 res[i] = stk.top() 即可。
最易被忽略的是:这个算法只适用于“严格更小”,若题目要“小于等于”,逻辑要整体翻转,且栈需维护递减序——别想当然复用同一段代码。


















