第48天,单调栈part01,栈的特殊应用场所!编程语言:C++
目录
739. 每日温度
496.下一个更大元素 I
503.下一个更大元素II
总结:
739. 每日温度
文档讲解:代码随想录每日温度
视频讲解:手撕每日温度
题目: 739. 每日温度 - 力扣(LeetCode)
学习:本题是单调栈的第一道题。本题直观的解决办法是采用双层循环,一层遍历当前元素,一层遍历后续元素并找到比当前元素大的值,以此来进行求解,时间复杂度为O(n^2)。
但本题可以采用单调栈的方式,降低时间复杂度,单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。
单调栈的主要使用场景就是寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置。最终实现的复杂度为O(n)。注意单调栈中存放的应该是遍历过的元素。
在每一题使用单调栈前我们需要注意两点:1. 单调栈里存放的元素是什么?2. 单调栈里的元素是递增呢?还是递减呢?(这个的递增递减是从栈顶往栈底看的)
在本题中,我们可以在单调栈中存放元素下标,以方便我们记录下一个更大元素的位置。同时我们要找的是比当前元素大的元素,因此我们单调栈应该采取递增的方式,比当前元素小,就加入栈中。
使用单调栈主要有三个判断条件。
- 当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
- 当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
- 当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况
代码:我们可以通过代码,直观感受单调栈的使用:
//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
vector<int> result(temperatures.size(), 0); //返回的答案数组
stack<int> st; //单调栈:适合查找左边或者右边,第一个比当前元素大或者小的元素
st.push(0); //放入第一个元素,注意放入的应该是下标
for(int i = 1; i < temperatures.size(); i++) {
if(temperatures[i] <= temperatures[st.top()]) { //如果当前元素比栈顶元素小,则加入栈中
st.push(i);
}
else { //如果当前元素比栈顶元素大,则要开始弹出
while(!st.empty() && temperatures[i] > temperatures[st.top()]) { //注意这个地方必须要用循环
result[st.top()] = i - st.top();
st.pop();
}
st.push(i);
}
}
return result;
}
};
代码:我们也可以将上述代码简化,因为无论如何我们都会有st.push(i)这一步存在。
//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
vector<int> result(temperatures.size(), 0); //返回的答案数组
stack<int> st; //单调栈:适合查找左边或者右边,第一个比当前元素大或者小的元素
st.push(0); //放入第一个元素,注意放入的应该是下标
for(int i = 1; i < temperatures.size(); i++) {
while(!st.empty() && temperatures[i] > temperatures[st.top()]) {
result[st.top()] = i - st.top();
st.pop();
}
st.push(i);
}
return result;
}
};
496.下一个更大元素 I
文档讲解:代码随想录下一个更大元素I
视频讲解:手撕下一个更大元素I
题目:496. 下一个更大元素 I - 力扣(LeetCode)
学习:本题也是找下一个更大元素,解题的核心思路是一样的。只不过本题还需要解决一个问题,就是nums1到nums2的映射问题,解决这个问题我们可以采用哈希表的方法,把nums1的元素加入到哈希表中,之后在nums2内通过查找哈希表的方式,进行相关元素的赋值。
这里要注意,我们遍历的一定是nums2,因为我们要找的是nums2中下一个更大元素。
同时本题单调栈中,我们可以存取下标(然后通过下标来找到对应元素),也可以存取元素,因为本题我们需要找的是最大元素,而不是下标位置。
代码:存取下标的方式
//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
stack<int> st;
vector<int> result(nums1.size(), -1);
if (nums1.size() == 0) return result;
unordered_map<int, int> umap; // key:下标元素,value:下标
for (int i = 0; i < nums1.size(); i++) {
umap[nums1[i]] = i;
}
st.push(0);
for (int i = 1; i < nums2.size(); i++) {
while (!st.empty() && nums2[i] > nums2[st.top()]) {
if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素
int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下标
result[index] = nums2[i];
}
st.pop();
}
st.push(i);
}
return result;
}
};
代码:存取元素的方式
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> result(nums1.size(), -1); //记录答案
unordered_map<int, int> umap; //使用哈希表构建nums1和nums2的关系
for(int i = 0; i < nums1.size(); i++) {
umap[nums1[i]] = i;
}
stack<int> st; //单调栈,栈顶到栈底从小到大
st.push(nums2[0]);
for(int i = 0; i < nums2.size(); i++) {
while(!st.empty() && nums2[i] > st.top()) {
if(umap.count(st.top()) != 0) {
result[umap[st.top()]] = nums2[i];
}
st.pop();
}
st.push(nums2[i]);
}
return result;
}
};
503.下一个更大元素II
文档讲解:代码随想录下一个元素更大II
视频讲解:手撕下一个元素更大II
题目: 503. 下一个更大元素 II - 力扣(LeetCode)
学习:本题相较于上一题其实更为简单,本题我们主要需要解决的是循环数组的问题。关于这个问题有两个求解办法。
1.通过扩展数组的方式,实现循环数组。也就是将两个nums数组拼接到一起,然后使用单调栈计算出每一个元素的下一个最大值,最后再把结果集即result数组resize到原数组大小就可以了。
代码:
//时间复杂度O(n)
//空间复杂度O(2n)
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
// 拼接一个新的nums
vector<int> nums1(nums.begin(), nums.end());
nums.insert(nums.end(), nums1.begin(), nums1.end());
// 用新的nums大小来初始化result
vector<int> result(nums.size(), -1);
if (nums.size() == 0) return result;
// 开始单调栈
stack<int> st;
st.push(0);
for (int i = 1; i < nums.size(); i++) {
if (nums[i] < nums[st.top()]) st.push(i);
else if (nums[i] == nums[st.top()]) st.push(i);
else {
while (!st.empty() && nums[i] > nums[st.top()]) {
result[st.top()] = nums[i];
st.pop();
}
st.push(i);
}
}
// 最后再把结果集即result数组resize到原数组大小
result.resize(nums.size() / 2);
return result;
}
};
2.也可以采用取余的方式进行循环数组求解,这个方式也是目前比较好的方式,后面碰到循环数组,都可以采取取余的方式进行求解。本质上就是通过取余来遍历数组两次。
代码:
//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
vector<int> result(nums.size(), -1); //记录答案返回数组
if(nums.size() == 0) return result;
stack<int> st; //单调栈
st.push(0);
for(int i = 0; i < nums.size() * 2; i++) { //使用取余的方法,模拟环形数组
while(!st.empty() && nums[i % nums.size()] > nums[st.top()]) { //单调栈处理逻辑
result[st.top()] = nums[i % nums.size()];
st.pop();
}
st.push(i % nums.size());
}
return result;
}
};
总结:
牢记单调栈应用场所,解决寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置的问题。