C++ 力扣刷题 4——单调栈

参考灵神的题单和视频
单调栈【基础算法精讲 26】
【视频】一个视频讲透单调栈!附题单(Python/Java/C++/Go/JS/Rust)
[数据结构]——单调栈
(若有侵权会及时删除,请联系告知!)

第一题

739.每日温度
在这里插入图片描述
在这里插入图片描述

单调栈模板

如灵神所说:及时去掉无用数据,保证栈中元素有序。

stack<int> s;
//此处一般需要给数组最后添加结束标志符,当然可以增加特例解决突发情况
for (遍历数组)
{
	if (栈空 || 栈顶元素大于等于当前比较元素)
	{
		入栈;
	}
	else
	{
		while (栈不为空 && 栈顶元素小于当前元素)
		{
			栈顶元素出栈;
			更新结果;
		}
		当前数据入栈;
	}
}

思路分析一:逆序单调栈法

以[1,4,3,2,5,2,3,6]为例,参考美丽塔,将其数据想象成一个个山,由于5的出现,2和3都被挡住了(成为了无用数据,于是我们需要将其从栈顶移出),再往左遍历,左边的山也看不到高度为2和3的山。

示例代码

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        //逆序做法
        stack<int> s;
        int n = temperatures.size();
        vector<int> ans(n,0);
        for(int i = n - 1; i >= 0; i--)
        {
            int t = temperatures[i];
            while(!s.empty() && t >= temperatures[s.top()]) //如果当前值大于等于栈顶值,则弹出,这是单调递增栈
                s.pop();
            if(!s.empty())
            {
                ans[i] = s.top() - i;
            }
            s.push(i);
        }
        return ans;
    }
};

思路分析二:正序单调栈法(更符合单调栈的特点)

单调递增栈,遇到比它大的第一个元素就将栈顶元素弹出,直到栈为空或者栈顶元素大于当前元素。

示例代码

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        //逆序做法
        stack<int> s;
        int n = temperatures.size();
        vector<int> ans(n,0);
        //正序做法
        for(int i = 0; i < n; i++)
        {
            int t = temperatures[i];
            while(!s.empty() && t > temperatures[s.top()])
            {
                int j = s.top();
                ans[j] = i - j;
                s.pop();
            }
            s.push(i);
        }
        return ans;
};

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),因为每个元素最多入栈一次,出栈一次,总共循环n次。
  • 空间复杂度: O ( m i n ( n , U ) ) , U = m a x − m i n + 1 。 O(min(n,U)), U = max - min + 1。 O(min(n,U)),U=maxmin+1

第二题

901.股票价格跨度
在这里插入图片描述
在这里插入图片描述

思路分析——参考灵神的题解

暴力做法:从当前位置往前找,直到找到一个大于 p r i c e price price 的数为止,即 p r i c e price price 的上一个更大元素。实际上,对于小于等于 p r i c e price price 的数 x x x ,不可能作为后续 n e x t next next 输入的数的上一个更大元素(因为 x ⩽ p r i c e x \leqslant price xprice且更远)。所以这些数(小于等于 p r i c e price price )是无用数据,应当移除。
代码实现时,可以在初始化时往栈底添加一个 ( − 1 , ∞ ) (-1,∞) (1,),这样栈一定不为空,无需单独处理 p r i c e price price 大于等于之前所有输入的情况。

示例代码——单调递减栈

class StockSpanner {
public:
    StockSpanner() {
        this->stk.emplace(-1, INT_MAX);
        this->idx = -1;
    }
    
    int next(int price) {
        while(price >= stk.top().second)
            stk.pop();
        int ans = ++idx - stk.top().first;
        stk.emplace(idx,price);
        return ans;
    }

private:
    stack<pair<int, int>> stk; 
    int idx;
};

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),因为每个元素最多入栈一次,出栈一次,总共循环n次。
  • 空间复杂度: O ( m i n ( q , U ) ) , U = m a x − m i n + 1 。 O(min(q,U)), U = max - min + 1。 O(min(q,U)),U=maxmin+1 U U U 表示 p r i c e price price 的范围。而 q q q n e x t next next 的调用次数。由于栈中没有重复元素,在 p r i c e price price 值域很小的情况下,空间复杂度主要取决于price的取值范围。

第三题(周赛第四题,困难题)

2454.下一个更大元素IV
在这里插入图片描述
在这里插入图片描述

思路分析——参考灵神和newhar大佬的题解

两种解法:单调栈 + 堆 / 排序 + 分组 + 有序集合(python3/c++)
O(n) 一次遍历+双单调栈(Python/Java/C++/Go)
思路一:
1、如何求下一个更大元素?
答:维护一个单调递增栈,从左向右遍历数组,如果栈顶元素小于 n u m s [ i ] nums[i] nums[i] ,那么 n u m s [ i ] nums[i] nums[i] 就是栈顶元素的下一个更大元素。
2、如何求右侧第二个更大元素?
答:观察单调栈求下一个更大元素的过程,当遍历到 n u m s [ i ] nums[i] nums[i] 时,所有被 n u m s [ i ] nums[i] nums[i] 弹出栈顶的元素,其右侧已经拥有了一个更大的元素。我们可以考虑将其丢到另一个排序好的数据结构(比如栈、map、有序集合)中,留待后续再找到一个更大元素。
具体而言,在后序遍历中, 如果 n u m s [ i ] > nums[i] > nums[i]> 集合(堆)中的最小元素,那么这个最小元素就又找到了一次右侧的更大元素,一直重复该动作直到堆中没有比 n u m s [ i ] nums[i] nums[i]更小的元素。
3、实现过程中,应首先维护堆,再维护单调栈。 (因为当一个元素被 n u m s [ i ] nums[i] nums[i] 弹出单调栈、放入堆中,不能再被 n u m s [ i ] nums[i] nums[i] 弹出第二次)。

思路一示例代码

class Solution {
public:
    vector<int> secondGreaterElement(vector<int>& nums) {
        int n = nums.size();
        priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> q; greater表示小顶堆,less表示大顶堆
        vector<int> ans(n,-1);
        stack<int> s;
        for(int i = 0; i < nums.size(); i++)
        {
            while(q.size() && nums[i] > q.top().first)
            {
                ans[q.top().second] = nums[i];
                q.pop();   
            }

            while(!s.empty() && nums[s.top()] < nums[i])
            {
                q.push( {nums[s.top()],s.top()});
                s.pop();
            }
            s.push(i);
        } 
        return ans;
    }
};
  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
  • 空间复杂度: O ( n ) O(n) O(n)

思路二:
下图摘自两种解法:单调栈 + 堆 / 排序 + 分组 + 有序集合(python3/c++)
在这里插入图片描述

思路二示例代码

class Solution {
public:
    vector<int> secondGreaterElement(vector<int>& nums) {
        int n = nums.size();
        vector<int> ans(n,-1),s,t;
        for(int i = 0; i < nums.size(); i++)
        {
            int x = nums[i];
            while(!t.empty() && nums[t.back()] < x)
            {
                ans[t.back()] = x;
                t.pop_back();
            }

            int j = (int)s.size() - 1;
            while(j >= 0 && nums[s[j]] < x) j--;
            t.insert(t.end(),s.begin() + (j + 1), s.end()); //注意这里j + 1不加括号的话下标访问会出错
            s.resize(j + 1);
            s.push_back(i);
        } 
        return ans;
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,关于力扣刷题C++常用操作,我可以给你一些常见的操作和技巧: 1. 使用 STL 容器和算法库:STL(Standard Template Library)是 C++ 标准库中的一个重要组成部分,包含了许多常用的容器和算法。在力扣刷题中,使用 STL 可以大大提高代码的效率和可读性。例如,vector 可以用来存储动态数组,sort 可以用来排序等等。 2. 使用 auto 关键字:auto 关键字可以自动推导变量类型,可以减少代码量和提高可读性。例如,auto x = 1; 可以自动推导出 x 的类型为 int。 3. 使用 lambda 表达式:lambda 表达式是 C++11 中引入的一种匿名函数,可以方便地定义一些简单的函数对象。在力扣刷题中,使用 lambda 表达式可以简化代码,例如在 sort 函数中自定义比较函数。 4. 使用位运算:位运算是一种高效的运算方式,在力扣刷题中经常会用到。例如,左移运算符 << 可以用来计算 2 的幂次方,右移运算符 >> 可以用来除以 2 等等。 5. 使用递归:递归是一种常见的算法思想,在力扣刷题中也经常会用到。例如,二叉树的遍历、链表的反转等等。 6. 使用 STL 中的 priority_queue:priority_queue 是 STL 中的一个容器,可以用来实现堆。在力扣刷题中,使用 priority_queue 可以方便地实现一些需要维护最大值或最小值的算法。 7. 使用 STL 中的 unordered_map:unordered_map 是 STL 中的一个容器,可以用来实现哈希表。在力扣刷题中,使用 unordered_map 可以方便地实现一些需要快速查找和插入的算法。 8. 使用 STL 中的 string:string 是 STL 中的一个容器,可以用来存储字符串。在力扣刷题中,使用 string 可以方便地处理字符串相关的问题。 9. 注意边界条件:在力扣刷题中,边界条件往往是解决问题的关键。需要仔细分析题目,考虑各种边界情况,避免出现错误。 10. 注意时间复杂度:在力扣刷题中,时间复杂度往往是评判代码优劣的重要指标。需要仔细分析算法的时间复杂度,并尽可能优化代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

highlight2333

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值