(一)栈和队列基础
SGI STL中,默认以deque为缺省情况下栈和队列的底层结构
deque是一个双向队列,只要封住一段,只开通另一端就可以实现栈的逻辑
可以指定vector为栈的底层实现,初始化语句
std::stack<int, std::vector<int>> third;
也可以指定list为队列底层实现,初始化语句
std::queue<int, std::list<int>> third;
(二)用栈实现队列
使用两个栈,一边出栈,一边入栈
这样改变元素的顺序,将原来在栈底的元素出栈,模拟出队列的行为
注意需要出栈的时候要把所有元素都先在另一个栈里入栈,保证顺序不改变
stackIn; stackOut;
void push(int x){
stackIn.push(x);
}
int pop(){
if(stackOut.empty(){
while(!stackIn.empty()){
stackOut.push(stackIn.top());
stackIn.pop();
}
result = stackOut.top();
stackOut.pop();
return result;
}
int peek(){
result = this -> pop();
//只查询,不弹出,所以调用后还需要放回
stackOut.push(result);
return result;
}
class MyQueue {
public:
stack<int> stIn;
stack<int> stOut;
MyQueue() {
}
void push(int x) {
stIn.push(x);
}
int pop() {
if(stOut.empty()){
while(!stIn.empty()){
stOut.push(stIn.top());
stIn.pop();
}
}
int result = stOut.top();
stOut.pop();
return result;
}
int peek() {
int result = pop();
stOut.push(result);
return result;
}
bool empty() {
return stIn.empty() && stOut.empty();
}
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue* obj = new MyQueue();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->empty();
*/
(三)用队列实现栈
用一个队列模拟栈
将需要弹出的元素前面的元素(size - 1)先都取出来再放进队列,直到最后一个元素变成第一个元素。
que
void push(int x){
que.push(x);
}
int pop(){
size = que.size();
size --;
//弹出size - 1 个元素
while(size --){
que.push(que.front());
que.pop();
}
int result = que.front();
que.pop();
return result;
}
int top(){
//栈顶即队尾
return que.back();
}
class MyStack {
public:
queue<int> que;
MyStack() {
}
void push(int x) {
que.push(x);
}
int pop() {
int size = que.size();
size --;
while(size --){
que.push(que.front());
que.pop();
}
int result = que.front();
que.pop();
return result;
}
int top() {
return que.back();
}
bool empty() {
return que.empty();
}
};
/**
* Your MyStack object will be instantiated and called as such:
* MyStack* obj = new MyStack();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->top();
* bool param_4 = obj->empty();
*/
(四)有效的括号
三种不匹配情况
① ( [ { } ] ( ) -----左括号多余 (字符串遍历完,栈不为空)
② [ { ( } ] -----括号不匹配 (和栈顶元素匹配不成功)
③ [ { } ] ( ) ) ) ) -----右括号多余 (字符串还没遍历完,栈为空了)
方法:
遇到 [ → ] 入栈
遇到 { → } 入栈
遇到 ( → )入栈
好处:如果将左括号入栈,那么出栈时,还需要有一次左括号和右括号的对应匹配
这样做只要判断入栈的元素和栈顶元素是否相同就可以了
stack<char> st;
//剪枝,如果为s长度奇数,则一定不匹配
if(s.size()%2 !=0) return false;
for(int i = 0; i < s.size(); i++){
if(s[i] == '(') st.push(')');
else if(s[i] == '[') st.push(']');
else if(s[i] == '{') st.push('}');
//先判定栈是否为空,否则st.top可能报错
// st.empty()针对第③种情况,st.top != s[i]针对第②种情况
else if(st.empty() || st.top() != s[i])
return false;
//匹配情况,消除栈顶元素
else st.pop();
}
//对应第①种异常,如果s遍历完栈不为空,返回false,不为空,则匹配,返回true
return st.empty();
}
class Solution {
public:
bool isValid(string s) {
stack<int> st;
if(s.size() % 2 != 0){
return false;
}
for(int i = 0; i < s.size(); i++){
if(s[i] == '(') st.push(')');
else if(s[i] == '[') st.push(']');
else if(s[i] == '{') st.push('}');
else if(st.empty() || st.top() != s[i]) return false;
else st.pop();
}
return st.empty();
}
};
(五)删除字符串中的所有相邻重复项
匹配问题:栈
方法一:建立一个新栈,类比括号匹配问题,但由于栈弹出的元素是倒叙的,需要最后对字符串反转。
class Solution {
public:
string removeDuplicates(string s) {
stack<int> st;
for(int i = 0; i< s.size(); i++){
if(!st.empty() && s[i] == st.top()){
st.pop();
}else{
st.push(s[i]);
}
}
string result = "";
while(!st.empty()){
result += st.top();
st.pop();
}
reverse(result.begin(),result.end());
return result;
}
};
方法二:直接用字符串作为栈,用字符串里的方法push_back和pop_back模拟出栈入栈,这样省去了栈要转为字符串的操作,也不用再反转。
class Solution {
public:
string removeDuplicates(string s) {
string result;
for(int i = 0; i < s.size(); i++){
if(result.empty() || result.back() != s[i]){
result.push_back(s[i]);
}else{
result.pop_back();
}
}
return result;
}
};
(六)逆波兰表达式求值
用栈解决,当遇到运算符,将栈顶的元素pop出来两个,按照运算符进行运算,直到所有的元素都被遍历过,输出栈顶元素,即为运算结果
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for(int i = 0; i < tokens.size(); i++){
//遇到运算符号
if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/"){
int num1 = st.top();
st.pop();
int num2 = st.top();
st.pop();
if(tokens[i] == "+") st.push(num2 + num1);
if(tokens[i] == "-") st.push(num2 - num1);
if(tokens[i] == "*") st.push(num2 * num1);
if(tokens[i] == "/") st.push(num2 / num1);
}else{
//这里注意因为vector里面存储的是string,要用stoi函数转为int
st.push(stoi(tokens[i]));
}
}
return st.top();
}
};
(七)滑动窗口最大值
滑动窗口的移动过程:队列
pop() :前面需要遗弃的元素
push() :后面需要加入的元素
getMaxValue() :每次获得队列中所有元素的最大值
实现单调队列(不同于优先级队列)
只需要维护滑动窗口中可能成为最大值的序列
①如果push入的元素前面的元素都比该元素小,则前面的元素都pop出去,只保留该元素在队列中
②如果push入的元素前面的元素都比该元素大,则将该元素直接push进队列
③每次滑动窗口后移,如果需要弹出的元素和队头元素相同,则pop队头元素,否则就不要pop(之前因为太小已经被pop出去了),只需要处理后面待push的元素,重复过程①②
使用deque为数据结构构造单调队列(C++中栈和队列底层实现逻辑都是deque)
deque<int> que; //deque为双向队列,左右都可出入元素
//val为需要pop掉的元素
pop(int val){
//判断需要pop的元素是和构造的单调队列的队头元素一致,此时需要弹出的元素为单调队列中的最大元素,所以在队头
if(!que.empty() && val == que.front()){
que.pop_front();
}
push(int val){
//对队列中的元素判断的前面都要先判断队列是否为空
//将队尾元素一直弹出,直到队尾元素比待push的元素大为止,再将该元素push进入队伍
while(!que.empty() && val > que.back()){
que.pop_back();
}
que.push_back(val);
}
//返回队首维护的最大元素
getMaxValue(){
return que.front();
}
class Solution {
public:
deque<int> que;
void pop(int val){
if(!que.empty() && que.front() == val){
que.pop_front();
}
}
void push(int val){
while(!que.empty() && que.back() < val){
que.pop_back();
}
que.push_back(val);
}
int getMaxValue(){
return que.front();
}
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> result;
int i = 0;
for(; i < k; i++){
push(nums[i]);
}
result.push_back(getMaxValue());
for(; i < nums.size(); i++){
push(nums[i]);
pop(nums[i-k]);
result.push_back(getMaxValue());
}
return result;
}
};
(八)前K个高频元素
两个难点:
①求数组每个元素求频率
②排序
使用map存放元素和频率,key为元素,value为出现的次数
按照value对元素排序,找到前k个
没必要对所有元素排序(nlogn),维护一个数据结构——大顶堆/小顶堆
大顶堆:根节点最大
小顶堆:根节点最小
用堆遍历map,只维护前k个元素的排序
如果用大顶堆,每次pop出的元素为堆顶的元素,这是最大的元素,等到数组所有元素遍历过后,大顶堆里留下的是前k个低频元素,与题意想左。
所以用小顶堆,每次pop出堆顶较小的元素,那么最后留下的是最大的元素。
最后把value排序后对应的key输出。
时间复杂度:
遍历数组:n
在小顶堆里加入元素:logk(因为小顶堆维护是二叉树,二叉树插入节点时间复杂度为logn)
C++中优先级队列的底层实现为堆,可以自定义大顶堆和小顶堆
map<int, int>
for(i = 0; i < nums.size; i++){
map[nums[i]] ++;
}
//定义优先级队列,小顶堆,cmp()自定义排序方式
priority_que(<key,value> cmp()){
for(map:: it<key,value>){
que.push(it);
//只维护前k个元素
if(que.size > k) que.pop();
}
vector<int> result;
//倒序输出,因为小顶堆里面pop出来的顺序是由小到大
for(int i = k-1; i >= 0; i--){
result[i] = que.top().first();
}
class Solution {
public:
class mycomparator{
public:
bool operator()(const pair<int,int>& lhs, const pair<int,int>& rhs){
//less是大顶堆,greater是小顶堆,这里构建小顶堆
return lhs.second > rhs.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> numMap;
for(int i = 0; i< nums.size(); i++){
numMap[nums[i]]++;
}
//priority_queue<type,content,cmp>,最小堆排序
priority_queue<pair<int,int>, vector<pair<int,int>>,mycomparator> pri_que;
//用迭代器遍历unordered_map
for(unordered_map<int,int>::iterator it = numMap.begin(); it != numMap.end(); it++){
pri_que.push(*it);
//只维护前k个元素
if(pri_que.size() > k){
pri_que.pop();
}
}
vector<int> result(k);
for(int i = k-1; i >= 0; i --){
result[i] = pri_que.top().first; //输出的map中的key值
pri_que.pop();
}
return result;
}
};