一.基础
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. 每日温度(中等,学习)
思想
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(简单,学习)
思想
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]
的下一个更大元素的值 ,所以是单调栈。因为这题nums1
是nums2
的子集,所以就可以遍历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. 股票价格跨度(中等)
思想
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. 车队(中等,学习)
思想
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();
}
};