【算法】-单调栈

目录

什么是单调栈

如何维护单调栈

单调栈的应用

例题


什么是单调栈

        单调栈是栈结构的一种变形,在满足栈先进后出(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) 。

对于要求解的这类问题,我们可以列一个简单的表格

求解的问题遍历方向维护单调性(栈底->栈顶)
左侧第一个更大从右到左单调递减
左侧第一个更小从右到左单调递增
右侧第一个更大从左到右单调递减
右侧第一个更小从左到右单调递增

例题

739. 每日温度 - 力扣(LeetCode)

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;
}

496. 下一个更大元素 I - 力扣(LeetCode)

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;
}

84. 柱状图中最大的矩形 - 力扣(LeetCode)

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;
}

单调栈是一种常用的数据结构,用于解决一类特定的问题,其中最常见的问题是找到数组中每个元素的下一个更大或更小的元素。在Codeforces编程竞赛中,单调栈经常被用于解决一些与数组相关的问题。 下面是单调栈的一般思路: 1. 创建一个空栈。 2. 从左到右遍历数组元素。 3. 对于每个元素,将其与栈顶元素进行比较。 - 如果当前元素小于等于栈顶元素,则将当前元素入栈。 - 如果当前元素大于栈顶元素,则将栈顶元素弹出,并将当前元素入栈。 4. 重复步骤3,直到遍历完所有元素。 这样,最后剩下的栈中元素就是没有下一个更大或更小元素的元素。在使用单调栈求解具体问题时,我们可以根据需要进行一些特定的操作。 例如,如果要找到一个数组中每个元素的下一个更大的元素,可以使用单调递减栈。具体操作如下: 1. 创建一个空栈和一个空结果数组。 2. 从左到右遍历数组元素。 3. 对于每个元素,将其与栈顶元素进行比较。 - 如果当前元素小于等于栈顶元素,则将当前元素入栈。 - 如果当前元素大于栈顶元素,则将栈顶元素弹出,并将其在结果数组中的位置记录为当前元素的下一个更大元素的索引。 4. 将当前元素入栈。 5. 重复步骤3和4,直到遍历完所有元素。 6. 结果数组中没有下一个更大元素的位置,可以设置为-1。 以下是一个使用单调递减栈求解下一个更大元素问题的示例代码: ```cpp #include <iostream> #include <stack> #include <vector> std::vector<int> nextGreaterElement(std::vector<int>& nums) { int n = nums.size(); std::vector<int> result(n, -1); std::stack<int> stack; for (int i = 0; i < n; i++) { while (!stack.empty() && nums[i] > nums[stack.top()]) { result[stack.top()] = i; stack.pop(); } stack.push(i); } return result; } int main() { std::vector<int> nums = {1,3, 2, 4, 5, 1}; std::vector<int> result = nextGreaterElement(nums); for (int i = 0; i < result.size(); i++) { std::cout << "Next greater element for " << nums[i] << ": "; if (result[i] != -1) { std::cout << nums[result[i]]; } else { std::cout << "None"; } std::cout << std::endl; } return 0; } ``` 以上代码将输出: ``` Next greater element for 1: 3 Next greater element for 3: 4 Next greater element for 2: 4 Next greater element for 4: 5 Next greater element for 5: None Next greater element for 1: None ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值