算法解析之单调栈

118 阅读7分钟

前言:

单调栈解决的问题:通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。要注意栈内元素的顺序,当加入元素的时候与栈顶元素进行比较(如果栈顶元素弹出了,就和下一个栈顶元素进行比较),弹出的元素就是已经找到了结果的元素,然后咋将元素压入找(压入栈一次最多压入一个,弹出时可以弹出多个)。最后栈中剩余的元素就是可以不用额外操作的元素(在初始化时解决)

1、每日温度:

题目描述:

请根据每日气温列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

题目难点:

如何在不使用暴力解法的基础上来解决问题?在遍历到这个元素的时候如何记录该元素以及该元素左边没有找到结果的元素?

解决办法:

本题其实就是找找到一个元素右边第一个比自己大的元素,此时就应该想到用单调栈了。单调栈的本质是空间换时间,就是用一个栈来记录我们遍历过的元素(但是没有找到右边较大值的元素)(例如本题是找该元素右边第一个比他大的元素,那么我们设置一个单调递增的栈,当输入的元素比栈顶元素小的时候,栈顶元素就找到了他右边的第一个比他大的值,就可以将栈顶元素的结果输出,如果依然比他小,那么后边的元素都未找到对应的结果,都需要等待下一个输入)。

1、单调栈里存放的元素是什么?

单调栈里只需要存放元素的下标i就可以了,如果需要使用对应的元素,直接T[i]就可以获取。

2、单调栈里元素是递增还是递减呢?

顺序的描述为 从栈头到栈底的顺序,如果求一个元素右边第一个更大元素,单调栈就是递增的,如果求一个元素右边第一个更小元素,单调栈就是递减的。举例说明:如果是递增栈,那么当输入元素i大于栈顶元素的值时,就会将栈顶元素弹出(相当于是栈顶元素找到了结果),栈顶元素的结果就是i,所以就是栈顶元素右边第一个比他大的元素就是T[i]。

最后分不同情况情况讨论:

1、当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况

2、当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况

3、当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况

对于栈中剩余的元素,在初始化的时候就已经将他们设置成0。(如果后续需要处理,那么就在弹出的时候进行修改,没有进行处理的话,就是默认没有找到右边比他大的元素。)

class Solution {
public:

    vector<int> dailyTemperatures(vector<int>& T) {

        // 递增栈

        stack<int> st;

        vector<int> result(T.size(), 0);

        st.push(0);

        for (int i = 1; i < T.size(); i++) {

            if (T[i] < T[st.top()]) {                       // 情况一

                st.push(i);

            } else if (T[i] == T[st.top()]) {               // 情况二

                st.push(i);

            } else {

                while (!st.empty() && T[i] > T[st.top()]) { // 情况三

                    result[st.top()] = i - st.top();

                    st.pop();

                }

                st.push(i);

            }

        }

        return result;

    }};

2、下一个更大的元素:

1、题目要求:

给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。 请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。 示例 1:

输入: nums1 = [4,1,2], nums2 = [1,3,4,2].

输出: [-1,3,-1]

解释:

对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。

对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。

对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

2、解题难点:

本题就是找到数组一中每一个元素在数组二中对应的元素,并找到这个对应的元素后面第一个比他大的元素。对于数组二中每个元素求后面第一个比他大的值,只需要使用单调栈进行遍历即可。本题的难点就是在数组二中找到较大值以后如何找到并修改数组一中对应的结果。(如何通过数组二中元素映射到数组一)。

3、解题思路:

我们可以通过map容器快速查找关键值的特点进行判断数组一中是否有该元素(题中说了每个元素最多出现一次,所以map容器中的key值不可能重复)。

定义unordered_map<int, int> umap;(这里是<下标对应元素, 下标>)通过这种定义方式,我们在对数组二中数据处理完以后,可以快速判断数组一中是否存在该元素,并且通过value值找到该元素的下标。(这里和定义数组的时候int a[下标] 刚好相反。)


我们整道题的解决思路:

1、首先将nums1中所有元素放入到map容器中,同时对应元素的value值代表了元素的下标(通过这种方式对元素在nums1进行定位)

2、利用单调栈找出nums2数组中每个元素后边第一个比他大的元素,当找到结果以后,我们就通过key值来查找该元素是否出现在nums1中,如果有的话就通过value值查找该元素对应的下标,对下标的值进行修改。

3、当所有元素都遍历过以后,单调栈中还存有一些未找到右边比他大的元素,因为在初始化的时候就默认赋值了未找到的情况,所以对于栈里的元素可以不做修改。

解题细节:

1、首先是定义一个和nums1一样大小的数组result来存放结果。同时将这个数组初始化为-1。(默认时找不到比他大的,如果找到了就在此基础上进行修改)。

vector<int> result(nums1.size(), -1);

2、在遍历nums2的过程中,我们要判断nums2[i]是否在nums1中出现过,因为最后是要根据nums1元素的下标来更新result数组。

创建一个map容器,将元素加入到map容器中,并用map容器的value值记录下标。我们就可以用map来做映射了。根据数值快速找到下标,还可以判断nums2[i]是否在nums1中出现过。

unordered_map<int, int> umap; // key:下标元素,value:下标

for (int i = 0; i < nums1.size(); i++) {

    umap[nums1[i]] = i;

}

3、通过一个单调递增的栈来找到右边第一个比他大的元素。当找到了以后判断栈顶元素是都也在nums1中出现过。如果出现了,根据该元素的value值来修改该元素所在位置的结果。

while (!st.empty() && nums2[i] > nums2[st.top()]) {

    if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素

        int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下标

        result[index] = nums2[i];

    }

    st.pop();

}

st.push(i);

完整代码:

class Solution {

public:

    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {

        stack<int> st;

        vector<int> result(nums1.size(), -1);

        if (nums1.size() == 0) return result;

 

        unordered_map<int, int> umap; // key:下标元素,value:下标

        for (int i = 0; i < nums1.size(); i++) {

            umap[nums1[i]] = i;

        }

        st.push(0);

        for (int i = 1; i < nums2.size(); i++) {

            if (nums2[i] < nums2[st.top()]) {           // 情况一

                st.push(i);

            } else if (nums2[i] == nums2[st.top()]) {   // 情况二

                st.push(i);

            } else {                                    // 情况三

                while (!st.empty() && nums2[i] > nums2[st.top()]) {

                    if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素

                        int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下标

                        result[index] = nums2[i];

                    }

                    st.pop();

                }

                st.push(i);

            }

        }

        return result;

    }

};