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

发布于:2025-08-07 ⋅ 阅读:(16) ⋅ 点赞:(0)

一.基础

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 > i 且 prices[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 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
对于每个 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.给定一个循环数组 nums ( nums[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 。
给定两个整数数组 position 和 speed ,长度都是 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();
    }
};

网站公告

今日签到

点亮在社区的每一天
去签到