单调栈

486 阅读2分钟

单调栈就是单调递增或者单调递减的栈结构,这种结构常用于处理的是下一个最大的元素、或者132模式这种数字的数组的某种顺序的问题。
先说第一个下一个最大的元素,

输入: [1,2,1]
输出: [2,-1,2]

就是给定一个数组,找到这个数组的每一个元素在其接下来的元素中比他大的第一个数,类似于高低个,这个人往后看可以看到的第一个比他高的那个人,这种时候就可以使用单调栈这种方法。
单调栈的思路就是从后往前去遍历数组,当前位置的数组元素和栈顶的元素来比较,如果比栈顶小,说明栈顶元素就是它往后看到的第一个比他高的人。为什么呢?因为我们遍历的顺序是逆序的,所以在栈中的元素是在这个元素的后面的元素,如果栈顶的元素比他大,那就是第一个大的元素了,所以代码如下。

var nextGreaterElements = function(nums) {
    let stack = [], res = [],len = nums.length;
    for (let i = len - 1; i>=0;i--) {
        let index = i;
        while(stack.length && stack[stack.length - 1] <= nums[index]) {
            stack.pop();
        }
        res[index] = stack.length ? stack[stack.length- 1] : -1;
        stack.push(nums[index]);
    }
    return res;
};

注意代码的顺序,是先获得遍历元素这个位置的下一个最大元素,随后再把当前的元素压入栈中。
那如果现在要求的是获得最大的那个元素与当前位置元素的差值呢?那最直接的就是我们不在使用value压入栈,而是使用index入栈。

var nextGreaterElements = function(nums) {
    let stack = [], res = [],len = nums.length;
    for (let i = len - 1; i>=0;i--) {
        while(stack.length && nums[stack[stack.length - 1]] <= nums[i]) {
            stack.pop();
        }
        res[i] = stack.length ? stack[stack.length- 1] - i : -1;
        stack.push(i);
    }
    return res;
};

另一个需要注意的是如果这个数组是循环数组的话该怎么办呢?那就是说当前元素的下一个最大元素可能在它之前?最好的办法就是把这个数组再复制一份放到这个数组的后面,这样比较就可以了,但是在实际中我们可以不用把这个数组复制一份,利用数组length%当前下标可以获得实际下标这一个特性,代码如下:

var nextGreaterElements = function(nums) {
    let stack = [], res = [],len = nums.length;
    for (let i = 2 * len - 1; i>=0;i--) {
        let index = i % len;
        while(stack.length && stack[stack.length - 1] <= nums[index]) {
            stack.pop();
        }
        res[index] = stack.length ? stack[stack.length- 1] : -1;
        stack.push(nums[index]);
    }
    return res;
};

另一个单调栈的使用是132模式,这个就是说数组中是否存在三个位置i,j,k,的元素,满足nums[i]<nums[k]<nums[j]这样的特性,单调栈处理这个的步骤是,既然是有一个后入的元素比当前元素小,类似单调,我们可以找到,找到之后就是在剩下的元素中找一个比先从栈中弹出去的元素小的数就返回true。 就是说首先入栈的k位置的元素,我们在从后往前遍历的过程中找到比栈顶大的,就是j位置的元素,现在找到了j会把k那个位置弹出去,随后只要找到一个比k位置小的i就可以了,具体代码如下:

var find132pattern = function(nums) {
    let stack = [],n = nums.length,k=Number.MIN_SAFE_INTEGER;
    for (let i = n-1;i>=0;i--) {
        if (nums[i] < k) return true; // 这就是为什么要先把k设置为最小的原因,在还没有得到k和j的值前,满足这个条件先不能返回
        while (stack.length && stack[stack.length - 1] < nums[i]) {
            k = stack.pop();
        }
        stack.push(nums[i]);
    }
    return false;
};

但是这个地方有一个需要注意的是当栈顶元素出栈的时候一定是当前元素比栈顶元素大,如果是等于也出栈的话,就说明j位置的元素和k位置的元素相等,这是不对的,所以这个地方要注意一下啊~