目录
什么是单调栈
单调栈是栈结构的一种变形,在满足栈先进后出(FILO)的条件下,还要满足栈内元素遵循单调性。比如从栈底到栈顶元素保持递增的单调递增栈。
如何维护单调栈
以维护一个单调递增栈为例。
当插入一个新元素时,为了维护栈内的单调性,我们将该元素与栈顶元素进行比较,若不满足单调性,就将栈顶元素弹出,不断重复,直到栈空或者满足单调性为止,最后再将该元素塞入栈顶。
写成代码就是这样子:
for(int i = 1; i <= n; i++)
{
while(top > 0 && stack[top] >= a[i]) {
top--;
}
sta[++top] = a[i];
}
当然,实际上我们更常用的是用单调栈保存元素的下标,而非其值——
for(int i = 1; i <= n; i++)
{
while(top > 0 && a[stack[top]] >=a [i]) {
top--;
}
stack[++top]=i;
}
单调栈的应用
单调栈最经典的应用,就是在一个数列里寻找距离元素最近的比其大/小的元素位置。
比如以下问题:
对数列中的每个元素,寻找其左侧第一个比它大的元素位置。
显而易见的,我们可以遍历每个元素,然后从其位置往左寻找,这样的暴力做法时间复杂度是O(n^2)。
但单调栈可以将时间复杂度降到O(n)。
我们只需从右往左遍历数列,依次将元素加入单调栈中,维护一个从栈底到栈顶递减的单调栈;
当某个元素被从栈内弹出时,代表它遇到了一个比它更大的元素,因为是从右往左遍历,所以该元素就是第一个比它大的元素,即所求。
如果最后仍在栈内,则说明该元素左侧没有比它更大的元素。
遍历的时间复杂度是 O(n) ,每个元素最多被加入单调栈一次、弹出来一次,所以总时间复杂度是O(n) 。
对于要求解的这类问题,我们可以列一个简单的表格
求解的问题 | 遍历方向 | 维护单调性(栈底->栈顶) |
---|---|---|
左侧第一个更大 | 从右到左 | 单调递减 |
左侧第一个更小 | 从右到左 | 单调递增 |
右侧第一个更大 | 从左到右 | 单调递减 |
右侧第一个更小 | 从左到右 | 单调递增 |
例题
int* dailyTemperatures(int* temperatures, int temperaturesSize, int* returnSize) {//找右边大左边小用单调栈
int* answer = (int *)malloc(temperaturesSize * sizeof(int));
// 初始化单调栈,这里使用数组模拟栈
int stack[temperaturesSize];
int top = -1; // 栈顶初始化为-1,表示栈为空
for (int i = 0; i < temperaturesSize; i++) {
//入栈元素大于栈头,栈头出栈,天数就等于下标之差
while (top != -1 && temperatures[i] > temperatures[stack[top]]) {
int index = stack[top--];// 取出栈顶元素
answer[index] = i - index;
}
stack[++top] = i;
}
// 遍历结束后,栈中剩余的元素都是无法找到更高温度的天数,直接赋值为0
while (top != -1) {
int index = stack[top--];
answer[index] = 0; // 无法找到更高温度,天数为0
}
*returnSize = temperaturesSize; // 返回结果数组的长度
return answer;
}
struct hash {
int key;
int value;
UT_hash_handle hh;
};
int* nextGreaterElement(int* nums1, int nums1Size, int* nums2, int nums2Size, int* returnSize)
{
struct hash *hashtable = NULL; // 初始表需要 = NULL
int stack[nums2Size]; // 声明单调栈
int top = -1; // 声明和初始化单调栈顶指针
// 一次遍历nums2数组,入栈所有元素,并且用hash存每个元素的下一个更大元素
for (int i = 0; i < nums2Size; i++) {
if (top == -1) { // 如果是空栈,直接入栈
stack[++top] = nums2[i];
continue;
}
if (nums2[i] < stack[top]) { // 如果取值小于栈顶数值,也直接入栈
stack[++top] = nums2[i];
continue;
}
// 只要取值一直大于栈顶数值,其栈顶数值出栈并和取值一同加入hash表中
while (top > -1 && nums2[i] > stack[top]) {
struct hash *tmp = malloc(sizeof(struct hash));
tmp->key = stack[top];
tmp->value = nums2[i];
HASH_ADD_INT(hashtable, key, tmp);
top--;//如果之后的数还是小于num2[i],拥有一样的下一个更大元素
}
stack[++top] = nums2[i]; // while出来后的取值还得进行入栈操作
}
int *res = malloc(sizeof(int) * nums1Size);
// 一次遍历nums1数组
for (int i = 0; i < nums1Size; i++) {
struct hash *tmp;
HASH_FIND_INT(hashtable, nums1 + i, tmp);//取出对应位置的hash表
if (tmp) { // 只要在hash表中,那就有对应的value值
res[i] = tmp->value;
} else { // 没在hash表中,赋值-1
res[i] = -1;
}
}
*returnSize = nums1Size;
return res;
}
503. 下一个更大元素 II - 力扣(LeetCode)
int* nextGreaterElements(int* nums, int numsSize, int* returnSize) {
int* answer = (int *)malloc(numsSize * sizeof(int));
*returnSize = numsSize;
// 初始化单调栈,这里使用数组模拟栈
int stack[numsSize];
int top = -1; // 栈顶初始化为-1,表示栈为空
int count = 0;
for(int i = 0; i < numsSize; i++) {
while(top > -1 && nums[stack[top]] < nums[i]) {
// 栈不为空且当前元素大于栈顶元素时,出栈并更新答案数组
answer[stack[top]] = nums[i];
--top;
}
// 当栈为空或当前元素小于等于栈顶元素时,将当前元素的索引入栈
stack[++top] = i;
}
// 处理循环数组的情况
for(int i = 0; i < numsSize && top > -1; i++) {
while(top > -1 && nums[stack[top]] < nums[i]) {
// 栈不为空且当前元素大于栈顶元素时,出栈并更新答案数组
answer[stack[top]] = nums[i];
--top;
}
}
// 剩余元素没有更大的值,置为-1
while(top > -1) {
answer[stack[top--]] = -1;
}
return answer;
}
int largestRectangleArea(int* heights, int heightsSize) {//本题目矩形向右形成,在当前下标
// 创建一个栈来保存柱形的索引
int stack[heightsSize + 1];
int top = -1;
// 初始化栈,将哨兵元素入栈,确保所有柱形都能被处理到
stack[++top] = -1;
// 用于存储最大面积
int maxArea = 0;
// 遍历每个柱形
for (int i = 0; i < heightsSize; i++) {
// 如果当前柱形高度小于等于栈顶柱形高度,说明找到了右边界
while (top > 0 && heights[i] <= heights[stack[top]]) {
// 计算以栈顶柱形为高度的矩形面积
int height = heights[stack[top--]]; // 取出栈顶柱形高度
int width = i - stack[top] - 1; // 宽度为当前索引减去栈顶元素索引再减去1
int area = height * width; // 计算矩形面积
maxArea = area > maxArea ? area : maxArea; // 更新最大面积
}
// 当前柱形入栈
stack[++top] = i;
}
// 处理栈中剩余的柱形
while (top > 0) {
int height = heights[stack[top--]]; // 取出栈顶柱形高度
int width = heightsSize - stack[top] - 1; // 宽度为数组大小减去栈顶元素索引再减去1,最后一个元素width相当于heightsSize
int area = height * width; // 计算矩形面积
maxArea = area > maxArea ? area : maxArea; // 更新最大面积
}
return maxArea;
}