一、单调栈介绍
单调栈非常使用的数据结构,在普通栈的基础上加以优化,使其更加完美的解决一些特殊的普通栈无法解决的问题。
单调栈顾名思义就是栈中元素从上到下是有顺序的,从栈底到栈顶递增我叫他--递增栈,反之递减栈;
从一段序列我们来认识单调栈,找到每一个数字右边第一个比其大的数,没有设为-1,如 序列 3 2 1 4 5 ,用递减栈来维护该序列,用同等大小的另一个数组存储对应位置的右边最近的大于该元素的数;
首先遍历数组,当栈为空或栈顶元素大于当前元素时,将当前元素下标依次加入栈中,此时栈中元素 0 (3) 1(2) 2(1);
遍历到 4 时当前元素大于栈顶下标对应元素 将当前元素存入答案数组的对应栈顶下标的位置,删除栈顶元素,重复判断若当前元素仍大于栈顶下标对应的元素,重复写入操作,否则将该元素下标加入栈中。
下面看力扣第 496 题
给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
二、例题
因为 数组1为2的子数组,且没有重复元素,所以先利用递减栈找出每一个元素的右侧第一个更大元素,以小的为键大的为值存入哈希表中;
将没有比其更大元素的元素值设为 - 1,然后遍历哈希表中的nums1数组,获取答案即可。
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> ans;
unordered_map<int, int> mp;
stack<int> s;
int len1 = nums1.size(), len2 = nums2.size();
//对 nums2 的每一个元素求右边第一个更大元素
int i = 0;
while(i < len2) {
if(!s.empty() && s.top() < nums2[i]) {
mp[s.top()] = nums2[i];
s.pop();
}else {
s.push(nums2[i]);
i++;
}
}
while(!s.empty()){
mp[s.top()] = -1;
s.pop();
}
for(int i = 0; i < len1; ++i) {
ans.push_back(mp[nums1[i]]);
}
return ans;
}
};
力扣第316题 去除重复字母
给你一个字符串
s
,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。首先分析一下题目,要按照字典序最小返回无重复字符串;
字典序:英语字典中的 A - Z 的顺序排列单词,越靠前越小,比如 abc 的字典序比 acb的小。
由此我们又一次想到了单调栈,当没出现重复元素时我们维持原来的序列,因为存在重复元素我们需要知道栈中元素是否重复,并且知道每个元素的个数,当只剩一个元素且栈中没有时,此元素就可以插入答案中。
准备工作:
计数数组 count :记录相同元素个数
标记数组 vis :标记当前元素是否在栈中
字符串模拟栈 stack:因为要返回字符串,直接用字符串模拟栈
。当元素不在栈中,栈顶元素大于当前元素
。此时判断字符串后面是否还有该元素
。若存在,说明当前元素不满足字典序,将其删除,标记置为0
。不存在直接返回
。将当前元素标记为1,并入栈
。将当前计数数组中的字符个数 - 1
。返回栈即为答案
#include<iostream>
#include<vector>
using namespace std;
string removeDuplicateLetters(string str) {
//普通数组不会像vector一样给初始值
int count[26] = {0}, vis[26] = {0};
//建立哈希表统计每个字符出现次数
for(char ch : str) {
count[ch - 'a']++;
}
string stack;
for(char ch : str) {
//记录字符是否在栈中
if(!vis[ch - 'a']) {
while(!stack.empty() && stack.back() > ch) {
if(count[stack.back() - 'a'] > 0) {
//如果字符串后面还有该字符,且该字符
//不满足字典序,删除并将访问记录置空
vis[stack.back() - 'a'] = 0;
//count[stack.back() - 'a']--;
stack.pop_back();
}else break;
}
//不在栈中再加入栈,然后标记
vis[ch - 'a'] = 1;
stack.push_back(ch);
}
//已遍历过的元素无论在不在栈中都要将已遍历元素个数--
count[ch - 'a'] -= 1;
}
return stack;
}
int main() {
string s = "cbacdcbc";
cout << removeDuplicateLetters(s) << endl;
return 0;
}
力扣第654题 最大二叉树
给定一个不含重复元素的整数数组 nums 。一个以此数组直接递归构建的 最大二叉树 定义如下:
1.二叉树的根是数组 nums 中的最大元素。
2.左子树是通过数组中 最大值左边部分 递归构造出的最大二叉树。
3.右子树是通过数组中 最大值右边部分 递归构造出的最大二叉树。
4.返回有给定数组 nums 构建的 最大二叉树 。
如数组 nums[3, 2, 1, 6, 0, 5]
由最大二叉树定义知
。从上到下最大的元素为根节点,依次向下减小
。在数组中最大元素左边的在左子树,右边的在右子树
。根节点最大
我们可以根据递减单调栈的性质来解决本道题。
。当栈为空或栈顶元素大于当前元素,将当前元素的节点指针入栈
。当栈顶元素小于当前元素的值时,临时保存栈顶元素,然后出栈
。若此时栈不为空,判断新栈顶元素与当前元素的大小
。新栈顶元素小于当前元素,因为新栈顶元素大于旧栈顶元素,并且为当前元素的左子树,按照定义旧栈顶元素为新栈顶元素的右孩子,新指向旧
。若栈为空或者新栈顶元素大于当前元素,说明旧栈顶元素为当前元素的左孩子
。遍历完成,栈中可能还存有元素
。因为遍历完整个左子树,根节点的右子树元素比栈顶元素小直接被加入栈中未完成指针连接,所以需要额外处理
。很容易发现规律,栈中剩余的树右子树递减元素,将栈顶的下一个元素指针指向栈顶,然后将头指针指向栈顶下一个元素即可,一个 while 循环即可解决。
给上单调栈和递归两种解法
class Solution {
public:
//方法二 单调栈
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
stack<TreeNode *> stk;
TreeNode *curNode = nullptr;
for(int num : nums) {
curNode = new TreeNode(num);
while(!stk.empty() && stk.top()->val < curNode->val) {
TreeNode *top = stk.top();
stk.pop();
if(!stk.empty() && stk.top()->val < curNode->val) {
stk.top()->right = top;
}else {
curNode->left = top;
}
}
stk.push(curNode);
}
while(!stk.empty()) {
curNode = stk.top();
stk.pop();
if(!stk.empty())stk.top()->right = curNode;
}
return curNode;
}
//方法一 递归法
TreeNode *construct(vector<int> &nums, int l, int r) {
if(l == r)return nullptr;
int max_i = max(nums, l, r);
TreeNode *root = new TreeNode(nums[max_i]);
root->left = construct(nums, l, max_i);
root->right = construct(nums, max_i + 1, r);
return root;
}
int max(vector<int> &nums, int l, int r) {
int max_i = l;
for(int j = l; j < r; ++j) {
if(nums[max_i] < nums[j])max_i = j;
}
return max_i;
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
return construct(nums, 0, nums.size());
}
};
喜欢别忘了支持,你们的肯定是我最大的动力。