每日算法刷题Day57:8.6:leetcode 单调栈6道题,用时2h

一.基础

1.套路

1.需要计算nums[i]的下一个更大/更小的下标/值(需要map映射),或者计算离的有多远/一个区间有多长[[七.单调栈#6. 853. 车队(中等)]][[七.单调栈#6. 853. 车队(中等,学习)]]

2.及时去掉无用数据,保证栈中元素有序

3.可分为两种情况,针对找右边的数从右往左遍历 就是存储下一个更大的数,从左往右遍历(跟找的顺序一样的好写) 就是存储未找到下一个更大的数的数
找左边的数反着来

(1)从右往左遍历

栈中存储下一个更大的数

复制代码
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> res(n, 0);
        vector<int> stk;
        // 从右往左遍历
        for (int i = n - 1; i >= 0; --i) {
            // 维护单调性
            while (!stk.empty() && temperatures[stk.back()] <= temperatures[i])
                stk.pop_back();
            // 存在下一个更大的数
            if (!stk.empty())
                res[i] = stk.back() - i;
            // 不存在下一个更大的数
            else
                res[i] = 0;
            stk.push_back(i);
        }
        return res;
    }
};

2.从左往右遍历(习惯写这个,符合思维 )

栈中存储未找到下一个更大的数的数

复制代码
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> res(n, 0);
        vector<int> stk;
        // 从左往右遍历
        for (int i = 0; i < n; ++i) {
            // 当前遍历元素是之前的下一个更大的数
            while (!stk.empty() && temperatures[stk.back()] < temperatures[i]) {
                res[stk.back()] = i - stk.back();
                stk.pop_back();
            }
            // 放入当前元素
            stk.push_back(i);
        }
        // 最后处理仍未找到下一个更大的数的数
        for(int& x:stk) res[x]=temperatures[x];
        return res;
    }
};
2. 题目描述
3. 学习经验

1.计算nums[i]的下一个更大/更小的值时需要map映射[[七.单调栈#3. 496. 下一个更大元素I(简单,学习)]]

2.看清楚是找右边的数还是找左边的数[[七.单调栈#5. 901. 股票价格跨度(中等)]]

3.栈中元素可以为pair

4.单调栈本质是在维护几个独立的区间,每个区间都是有序的 ,但在栈中只留下极值元素[[七.单调栈#6. 853. 车队(中等,学习)]],所以其他元素在遍历的时候视作无用元素,要弹出栈,这些区间的划分肯定是有序的,所以栈是单调的。且每个区间在遍历的时候只有一个元素就能算当前区间长度了[[七.单调栈#5. 901. 股票价格跨度(中等)]]

1. 739. 每日温度(中等,学习)

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

思想

1.给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

2.写法一:从右到左遍历

栈中元素为下一个更大的数 ,如下图所示,当前从右遍历到5,比2和3都大,5左边的数右侧更大的数如果可以为2和3,那必定取5 ,所以2和3被移出栈

![[每日温度从右往左.jpg]]

3.写法二:从左到右遍历

栈中元素为没有找到下一个更大的数的数 ,如下图所示从左到右遍历到5,然后遍历2,5还未找到下一个更大的数,所以5不能出栈

![[每日温度从左往右.jpg]]

代码

1.从右往左遍历

复制代码
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> res(n, 0);
        vector<int> stk;
        // 从右往左遍历
        for (int i = n - 1; i >= 0; --i) {
            // 维护单调性
            while (!stk.empty() && temperatures[stk.back()] <= temperatures[i])
                stk.pop_back();
            // 存在下一个更大的数
            if (!stk.empty())
                res[i] = stk.back() - i;
            // 不存在下一个更大的数
            else
                res[i] = 0;
            stk.push_back(i);
        }
        return res;
    }
};

2.从左往右遍历

复制代码
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> res(n, 0);
        vector<int> stk;
        // 从左往右遍历
        for (int i = 0; i < n; ++i) {
            // 当前遍历元素是之前的下一个更大的数
            while (!stk.empty() && temperatures[stk.back()] < temperatures[i]) {
                res[stk.back()] = i - stk.back();
                stk.pop_back();
            }
            // 放入当前元素
            stk.push_back(i);
        }
        return res;
    }
};
2. 1475. 商品折扣后的最终价格(简单)

1475. 商品折扣后的最终价格 - 力扣(LeetCode)

思想

1.给你一个数组 prices ,其中 prices[i] 是商店里第 i 件商品的价格。

商店里正在进行促销活动,如果你要买第 i 件商品,那么你可以得到与 prices[j] 相等的折扣,其中 j 是满足 j > iprices[j] <= prices[i]最小下标 ,如果没有满足条件的 j ,你将没有任何折扣。

请你返回一个数组,数组中第 i 个元素是折扣后你购买商品 i 最终需要支付的价格。

代码
复制代码
class Solution {
public:
    vector<int> finalPrices(vector<int>& prices) {
        int n = prices.size();
        vector<int> res(n, 0);
        vector<int> stk;
        for (int i = 0; i < n; ++i) {
            while (!stk.empty() && prices[stk.back()] >= prices[i]) {
                res[stk.back()] = prices[stk.back()] - prices[i];
                stk.pop_back();
            }
            stk.push_back(i);
        }
        for (int& x : stk)
            res[x] = prices[x];
        return res;
    }
};
3. 496. 下一个更大元素I(简单,学习)

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

思想

1.nums1 中数字 x下一个更大元素 是指 xnums2 中对应位置 右侧第一个x 大的元素。

给你两个 没有重复元素 的数组 nums1nums2 ,下标从 0 开始计数,其中nums1nums2 的子集。

对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j]下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1

返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素

2.需要计算 nums2[i]的下一个更大元素的值 ,所以是单调栈。因为这题nums1nums2的子集,所以就可以遍历nums2,通过判断当前元素在不在nums1中来决定入不入栈 。但因为是值,所以还需要一个 **map映射nums1的值-下标 **

代码
复制代码
class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        int n1 = nums1.size(), n2 = nums2.size();
        vector<int> res(n1, -1);
        map<int, int> idxs; // nums1值-下标
        for (int i = 0; i < n1; ++i)
            idxs[nums1[i]] = i;
        vector<int> stk;
        for (int& x : nums2) {
            while (!stk.empty() && x > stk.back()) {
                res[idxs[stk.back()]] = x;
                stk.pop_back();
            }
            // 如果x在nums1中
            if (idxs.find(x) != idxs.end())
                stk.push_back(x);
        }
        return res;
    }
};
4. 503.下一个更大元素II(中等)

503. 下一个更大元素 II - 力扣(LeetCode)

思想

1.给定一个循环数组 numsnums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素

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

2.循环就想到遍历两次,然后下标取余即可。但是每个元素下标还是只入一次栈,因为只更新一次答案

代码
复制代码
class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        int n = nums.size();
        vector<int> res(n, -1);
        vector<int> stk;
        for (int i = 0; i < 2 * n; ++i) {
            while (!stk.empty() && nums[i % n] > nums[stk.back()]) {
                res[stk.back()] = nums[i % n];
                stk.pop_back();
            }
            if (i < n)
                stk.push_back(i);
        }
        return res;
    }
};
5. 901. 股票价格跨度(中等)

901. 股票价格跨度 - 力扣(LeetCode)

思想

1.设计一个算法收集某些股票的每日报价,并返回该股票当日价格的 跨度

当日股票价格的 跨度 被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。

  • 例如,如果未来 7 天股票的价格是 [100,80,60,70,60,75,85],那么股票跨度将是 [1,1,1,2,1,4,6]
    实现 StockSpanner 类:
  • StockSpanner() 初始化类对象。
  • int next(int price) 给出今天的股价 price ,返回该股票当日价格的 跨度
    2.这题是找左边的数,所以按道理来说从右往左栈储存未找到答案的数的数更好写,但是题目要从左往右一个一个输入,然后一个一个输出,只能从左往右栈储存下一个更小的数,但因为是连续日数,所以要记录左边比当前价格低的数的最大连续日数,进行累加
    3.优化建议:这题要计算当前price离上一个更大元素有多远 ,所以比它小的直接出栈,他们是无用元素 ,因为要算距离,所以要记录下标,且还要知道值,可以用数组+索引变量+栈,也可以用pair栈+索引变量来解决
代码
复制代码
class StockSpanner {
public:
    vector<int> prices; // 记录值
    vector<int> cnts;   // 记录日数
    int id;             //  当前遍历下标
    vector<int> stk;
    StockSpanner() {
        prices.clear();
        id = 0;
    }

    int next(int price) {
        int cnt = 1;
        while (!stk.empty() && !prices.empty() && price >= prices[stk.back()]) {
            cnt += cnts[stk.back()];
            stk.pop_back();
        }
        prices.push_back(price);
        stk.push_back(id);
        cnts.push_back(cnt);
        ++id;
        return cnt;
    }
};

/**
 * Your StockSpanner object will be instantiated and called as such:
 * StockSpanner* obj = new StockSpanner();
 * int param_1 = obj->next(price);
 */

优化:

复制代码
class StockSpanner {
public:
    typedef pair<int, int> PII; // 索引-值
    vector<PII> stk;
    int id;
    StockSpanner() {
        stk.clear();
        id = 0;
    }

    int next(int price) {
        while (!stk.empty() && price >= stk.back().second) {
            stk.pop_back();
        }
        // 为空为[0,id]
        int res = id + 1;
        // (stk.back().first,id]
        if (!stk.empty())
            res = id - stk.back().first;
        stk.push_back({id, price});
        ++id;
        return res;
    }
};

/**
 * Your StockSpanner object will be instantiated and called as such:
 * StockSpanner* obj = new StockSpanner();
 * int param_1 = obj->next(price);
 */
6. 853. 车队(中等,学习)

853. 车队 - 力扣(LeetCode)

思想

1.在一条单行道上,有 n 辆车开往同一目的地。目的地是几英里以外的 target

给定两个整数数组 positionspeed ,长度都是 n ,其中 position[i] 是第 i 辆车的位置, speed[i] 是第 i 辆车的速度(单位是英里/小时)。

一辆车永远不会超过前面的另一辆车,但它可以追上去,并以较慢车的速度在另一辆车旁边行驶。
车队 是指并排行驶的一辆或几辆汽车。车队的速度是车队中 最慢 的车的速度。

即便一辆车在 target 才赶上了一个车队,它们仍然会被视作是同一个车队。

返回到达目的地的车队数量 。

2.这题要算车队数,那怎么样能算作一个车队呢,即看各车辆单独到目的地的时间,如果处在一个区间即为一个车队,而这个区间是单调递增的,表示前面的车比后面的车快,最后剩下几个区间就是几个车队。而维护一个区间从左往右遍历就是当前时间比栈顶元素时间长,则移除无用元素 ,只算当前时间一个车队。

但是这个从左到右遍历是以距离为度量的,所以得先把题目数组按照距离升序,考虑有两个元素,直接用结构体更方便。

代码
复制代码
class Solution {
public:
    struct Node {
        int pos;
        int spe;
        Node(int _pos, int _spe) : pos(_pos), spe(_spe) {}
        // 按位置排序
        bool operator<(const Node& other) { return pos < other.pos; }
    };
    vector<Node> nodes;
    int carFleet(int target, vector<int>& position, vector<int>& speed) {
        int n = position.size();
        for (int i = 0; i < n; ++i) {
            Node node(position[i], speed[i]);
            nodes.push_back(node);
        }
        // 按位置排序
        sort(nodes.begin(), nodes.end());
        vector<double> times(n);
        // 算时间
        for (int i = 0; i < n; ++i) {
            times[i] = 1.0 * (target - nodes[i].pos) / nodes[i].spe;
        }
        vector<double> stk;
        for (int i = 0; i < n; ++i) {
            // 前面的车可以追上后面,前面的算作无用元素,只算为一个车队
            while (!stk.empty() && times[i] >= stk.back()) {
                stk.pop_back();
            }
            stk.push_back(times[i]);
        }
        return stk.size();
    }
};
相关推荐
沐怡旸14 分钟前
【算法】【链表】328.奇偶链表--通俗讲解
算法·面试
掘金安东尼3 小时前
Amazon Lambda + API Gateway 实战,无服务器架构入门
算法·架构
码流之上4 小时前
【一看就会一写就废 指间算法】设计电子表格 —— 哈希表、字符串处理
javascript·算法
快手技术6 小时前
快手提出端到端生成式搜索框架 OneSearch,让搜索“一步到位”!
算法
CoovallyAIHub1 天前
中科大DSAI Lab团队多篇论文入选ICCV 2025,推动三维视觉与泛化感知技术突破
深度学习·算法·计算机视觉
NAGNIP1 天前
Serverless 架构下的大模型框架落地实践
算法·架构
moonlifesudo1 天前
半开区间和开区间的两个二分模版
算法
moonlifesudo1 天前
300:最长递增子序列
算法
CoovallyAIHub1 天前
港大&字节重磅发布DanceGRPO:突破视觉生成RLHF瓶颈,多项任务性能提升超180%!
深度学习·算法·计算机视觉
CoovallyAIHub1 天前
英伟达ViPE重磅发布!解决3D感知难题,SLAM+深度学习完美融合(附带数据集下载地址)
深度学习·算法·计算机视觉