每日算法刷题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();
    }
};
相关推荐
Vesan,1 小时前
无人机开发分享——基于行为树的无人机集群机载自主决策算法框架搭建及开发
c++·算法·决策树·无人机
爱coding的橙子1 小时前
每日算法刷题Day58:8.7:leetcode 单调栈5道题,用时2h
算法·leetcode·职场和发展
Monika Zhang2 小时前
【面试攻略】回答Java面试问题「挑战与解决方案」技巧
面试·职场和发展
董莉影2 小时前
学习嵌入式第二十二天
数据结构·学习·算法·链表
Lukeding2 小时前
Magnetic-UI源码解析
算法
chirrupy_hamal3 小时前
排序算法详解
算法
BOB_BOB_BOB_3 小时前
【ee类保研面试】数学类---概率论
面试·职场和发展·概率论·保研
csdn_aspnet3 小时前
四边形面积
算法·四边形
GawynKing3 小时前
图论(1):图数据结构
数据结构·算法·图论·图形理论