LeetCode:496/503 下一个更大元素 I / II 单调栈及单调栈性质总结

关于单调栈的其他题目:

单调栈的用处是快速找到左右边第一个小于/大于栈顶元素的元素

【LeetCode:402 移掉k位数字】
【LeetCode:84. 柱状图中最大的矩形】

下一个更大元素 I

给定两个没有重复元素的数组 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:
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]

解释:

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

注意:
nums1和nums2中所有元素是唯一的。
nums1和nums2 的数组大小都不超过1000。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-greater-element-i
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

直接对nums2中的每个元素,计算其右边第一个大于它的元素,存答案进哈希表,然后根据nums1的元素做key来读取哈希表即可

对于这个题,一般做法是枚举起始点,向右遍历找到第一个大于起点的值,这样的复杂度是O(n^2)

其实我们可以维护一个递减的单调栈,即栈顶的元素是最小的,然后开始遍历

  • 如果找到元素比栈顶小,直接进栈,因为新元素进栈之前,栈顶元素还没找到右边第一个比他大的元素
  • 如果找到一个元素比栈顶大,那么栈顶元素找到了第一个大于它的元素,可以更新答案,弹出栈顶,尝试更新新栈顶,直到栈空或者栈顶大于新元素(无法更新了
  • 新元素进栈

后处理:
如果栈中元素有剩下,说明右边没有更大的,答案直接置-1

class Solution {
     
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2)
    {
     
        unordered_map<int, int> hash;
        stack<int> s;
        for(int i=0; i<nums2.size(); i++)
        {
     
        	// 一直弹出尝试更新多个栈顶元素的答案
            while(!s.empty() && s.top()<=nums2[i])
                hash[s.top()]=nums2[i], s.pop();
            s.push(nums2[i]); hash[nums2[i]]=-1;
        }
        // 后处理:右边没有更大的,答案置-1
        while(!s.empty()) hash[s.top()]=-1, s.pop();
        for(int i=0; i<nums1.size(); i++)
            nums1[i] = hash[nums1[i]];
        return nums1;
    }
};

下一个更大元素 II

给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。

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

解释:

第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数; 
第二个 1 的下一个最大的数需要循环搜索,结果也是 2

注意: 输入数组的长度不会超过 10000。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-greater-element-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

寻找右边

和 下一个更大元素 I 一样,先是在右边找,维护单调栈并更新栈顶元素的答案

关键是循环,因为第一次找寻结束后,如果栈非空,说明栈中的元素都是右边没有比他更小的值了,这个时候应该在左边寻找第一个大于的元素了

寻找左边

从0开始,到最右边的下标之前(也就是栈顶元素的下标),重新跑一遍上面的更新,规则一样

后处理
如果两次循环都没有更新到答案,那么答案就是-1,这个后处理可以通过把答案数组初始化为-1来解决

不同点

因为答案需要元素一一对应,那么我们的栈存储的就应该是元素下标了,而不是元素,然后我们按照下标可以更新答案数组

代码

class Solution {
     
public:
    vector<int> nextGreaterElements(vector<int>& nums)
    {
     
        vector<int> ans(nums.size(), -1);
        stack<int> s;
        // 找右边
        for(int i=0; i<nums.size(); i++)
        {
     
            while(!s.empty() && nums[s.top()]<nums[i])
                ans[s.top()]=nums[i], s.pop();
            s.push(i);
        }
        // 如果右边全都有比它小的,那么得到了答案
        if(s.empty()) return ans;
        // 如果仍然存在【在右边未找到比他小的元素】的元素,找左边
        int stop = s.top();
        for(int i=0; i!=stop; i++)
        {
     
            while(!s.empty() && nums[s.top()]<nums[i])
                ans[s.top()]=nums[i], s.pop();
        }
        return ans;
    }
};

总结

相比于一般的枚举点作为最值,然后左右扩散,寻找边界,单调栈的引入,相当于反过来,枚举边界,然后看看有没有合适的最值,一个边界可以同时更新多个最值,就省略了重复的枚举

栈的单调性保证了左边的单调,更新答案必当发生在新元素与栈顶及其左边的单调方向不一致时,两边保持相反方向的单调,那么就保证了栈顶元素的最值性

单调栈擅长快速地找到

  • 某元素第一个 大于 / 小于 它的元素
  • 某元素第一个 大于 / 小于 它的元素

对于上面两个题,都是单边找第一个大值

双边缘

对于某些题目,比如一个区间的答案和【区间最值,区间长度】有关,那么应该是单调栈了

快速找左右第一个大于x的值,可以确定x是某个区间的极小值(极大值同理),然后更新答案

比如一个区间的得分是区间长度*区间最大值,那么就要通过单调栈快速找到两个边界,然后更新答案

以上。。

你可能感兴趣的:(LeetCode,数据结构,leetcode,数据结构,算法,栈,哈希表)